/*
 * Copyright 2018 Google
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import <XCTest/XCTest.h>

#import <GoogleUtilities/GULSwizzler.h>

#import <GoogleUtilities/GULSwizzler+Unswizzle.h>
#import <OCMock/OCMock.h>
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
#import "FirebaseDynamicLinks/Sources/FIRDLDefaultRetrievalProcessV2.h"
#import "FirebaseDynamicLinks/Sources/FIRDLRetrievalProcessFactory.h"
#import "FirebaseDynamicLinks/Sources/FIRDLRetrievalProcessResult+Private.h"
#import "FirebaseDynamicLinks/Sources/FIRDynamicLink+Private.h"
#import "FirebaseDynamicLinks/Sources/FIRDynamicLinkNetworking+Private.h"
#import "FirebaseDynamicLinks/Sources/FIRDynamicLinks+FirstParty.h"
#import "FirebaseDynamicLinks/Sources/FIRDynamicLinks+Private.h"
#import "FirebaseDynamicLinks/Sources/Utilities/FDLUtilities.h"
#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"

static NSString *const kAPIKey = @"myAPIKey";
static NSString *const kStructuredLinkFmtFreeform = @"%@://google/link/%@";
static NSString *const kStructuredLinkFmtDeepLink = @"%@://google/link/?deep_link_id=%@";
static NSString *const kStructuredLinkFmtInvitation = @"%@://google/link/?invitation_id=%@";
static NSString *const kStructuredLinkFmtInvitationWeak =
    @"%@://google/link/?invitation_id=%@&match_type=weak";
static NSString *const kStructuredLinkFmtDeepLinkAndInvitation =
    @"%@://google/link/?deep_link_id=%@&invitation_id=%@";
static NSString *const kStructuredUniversalLinkFmtFreeForm = @"https://goo.gl/app/sample%@";
static NSString *const kStructuredUniversalLinkFmtDeepLink =
    @"https://goo.gl/app/sample?link=%@&isi=585027354";
static NSString *const kStructuredUniversalLinkFmtSubdomain = @"https://sample.page.link%@";
static NSString *const kStructuredUniversalLinkFmtSubdomainDeepLink =
    @"https://sample.page.link?link=%@&isi=585027354";
static NSString *const kURLScheme = @"gindeeplinkurl";

static const NSTimeInterval kAsyncTestTimeout = 5.0;

/**
 * This string was generated by percent-encoding the Tactile URL for the Tokyo American Club in
 * Tokyo, and then replacing a '%2B' with a '+' to verify that the '+' does not cause our parsing to
 * fail and double-encoding one value to verify that only one decoding pass is run.
 */
NSString *kEncodedComplicatedURLString =
    @"https%3A%2F%2Fwww.google.com%252Fmaps%2Fplace%2FTokyo+Am"
    @"erican%2BClub%2F%4035.658578%2C139.741588%2C3a%2C75y%2C90t%2Fdata%3D%213m8%211e2%213m6%211s42"
    @"66698%212e1%213e10%216s%252F%252Fstorage.googleapis.com%252Fstatic.panoramio.com%252Fphotos%2"
    @"52Fmedium%252F4266698.jpg%217i640%218i480%214m2%213m1%211s0x0000000000000000%3A0x1b8b8130c791"
    @"48e1%216m1%211e1";
/** This string was generated by percent-decoding kEncodedComplicatedURLString. */
NSString *kDecodedComplicatedURLString =
    @"https://www.google.com%2Fmaps/place/Tokyo+American+Club/"
    @"@35.658578,139.741588,3a,75y,90t/data=!3m8!1e2!3m6!1s4266698!2e1!3e10!6s%2F%2Fstorage.googlea"
    @"pis.com%2Fstatic.panoramio.com%2Fphotos%2Fmedium%2F4266698.jpg!7i640!8i480!4m2!3m1!1s0x000000"
    @"0000000000:0x1b8b8130c79148e1!6m1!1e1";

static void *kOpenURLHandlerKey = &kOpenURLHandlerKey;

typedef NSURL * (^FakeShortLinkResolverHandler)(NSURL *shortLink);

@interface FIRDynamicLinks (FIRApp)
- (void)configureDynamicLinks:(FIRApp *)app;
- (BOOL)setUpWithLaunchOptions:(nullable NSDictionary *)launchOptions
                        apiKey:(NSString *)apiKey
                     urlScheme:(nullable NSString *)urlScheme
                  userDefaults:(nullable NSUserDefaults *)userDefaults;
- (BOOL)canParseUniversalLinkURL:(nullable NSURL *)url;
- (void)passRetrievedDynamicLinkToApplication:(NSURL *)url;
- (BOOL)isOpenUrlMethodPresentInAppDelegate:(id<UIApplicationDelegate>)applicationDelegate;
@end

@interface FakeShortLinkResolver : FIRDynamicLinkNetworking
+ (instancetype)resolverWithBlock:(FakeShortLinkResolverHandler)resolverHandler;
@end

@implementation FakeShortLinkResolver {
  FakeShortLinkResolverHandler _resolverHandler;
}

+ (instancetype)resolverWithBlock:(FakeShortLinkResolverHandler)resolverHandler {
  // The parameters don't matter since they aren't validated or used here.
  FakeShortLinkResolver *resolver = [[self alloc] initWithAPIKey:@"" URLScheme:@""];
  resolver->_resolverHandler = [resolverHandler copy];
  return resolver;
}

- (void)resolveShortLink:(NSURL *)url
           FDLSDKVersion:(NSString *)FDLSDKVersion
              completion:(FIRDynamicLinkResolverHandler)completion {
  if (_resolverHandler && completion) {
    NSURL *resolvedLink = _resolverHandler(url);
    completion(resolvedLink, nil);
  }
}

@end

// dummy protocol to prevent compile warning
@protocol DummyProtocol <NSObject>

@property(atomic, assign) BOOL retrievingPendingDynamicLink;

@property(nonatomic, readonly) FIRDynamicLinkNetworking *dynamicLinkNetworking;

- (void)handlePendingDynamicLinkRetrievalFailureWithErrorCode:(NSInteger)errorCode
                                             errorDescription:(NSString *)errorDescription
                                              underlyingError:(nullable NSError *)underlyingError;

@end

// Swizzle DynamicLinks.dynamicLinkNetworking property to return fake resolver.
static void SwizzleDynamicLinkNetworking(id linkResolver) {
  id (^dynamicLinkNetworkingBlock)(void) = ^id(void) {
    return linkResolver;
  };
  [GULSwizzler swizzleClass:[FIRDynamicLinks class]
                   selector:@selector(dynamicLinkNetworking)
            isClassSelector:NO
                  withBlock:dynamicLinkNetworkingBlock];
}

static void SwizzleDynamicLinkNetworkingWithMock(void) {
  id linkResolver = OCMPartialMock([[FIRDynamicLinkNetworking alloc] initWithAPIKey:kAPIKey
                                                                          URLScheme:kURLScheme]);
  [[linkResolver stub] resolveShortLink:OCMOCK_ANY FDLSDKVersion:@"1.0.0" completion:OCMOCK_ANY];

  SwizzleDynamicLinkNetworking(linkResolver);
}

static void UnswizzleDynamicLinkNetworking(void) {
  [GULSwizzler unswizzleClass:[FIRDynamicLinks class]
                     selector:@selector(dynamicLinkNetworking)
              isClassSelector:NO];
}

@interface FIRDynamicLinksTest : XCTestCase {
  id _bundleMock;
}

// An instance of |GINDurableDeepLinkService| used for testing.
@property(nonatomic, strong) FIRDynamicLinks *service;
// An instance of |NSUserDefaults| that have all default values removed.
@property(nonatomic, strong) NSUserDefaults *userDefaults;
// FIRAnalytics mock. Necessary because we don't call [FIRAPP configure].
@property(nonatomic, strong) id analytics;

@end

@implementation FIRDynamicLinksTest

// Disable deprecated warning for internal methods.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"

#pragma mark - Test lifecycle

static NSString *const kInfoPlistCustomDomainsKey = @"FirebaseDynamicLinksCustomDomains";

- (void)setUp {
  [super setUp];

  // Mock the mainBundle infoDictionary with version from DL-Info.plist for custom domain testing.
  NSBundle *bundle = [NSBundle bundleForClass:[self class]];
  NSString *filePath = [bundle pathForResource:@"DL-Info" ofType:@"plist"];
  _bundleMock = OCMPartialMock([NSBundle mainBundle]);
  OCMStub([_bundleMock infoDictionary])
      .andReturn([NSDictionary dictionaryWithContentsOfFile:filePath]);

  if (![FIRApp isDefaultAppConfigured]) {
    XCTAssertNoThrow([FIRApp configureWithOptions:[self appOptions]]);
  }

  self.service = [[FIRDynamicLinks alloc] init];
  self.userDefaults = [[NSUserDefaults alloc] init];
  [self.userDefaults removePersistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]];
  self.analytics = OCMProtocolMock(@protocol(FIRAnalyticsInterop));
}

- (void)tearDown {
  self.service = nil;
  self.userDefaults = nil;
  [self.analytics stopMocking];
  self.analytics = nil;
  [_bundleMock stopMocking];
  _bundleMock = nil;
  [super tearDown];
}

- (FIROptions *)appOptions {
  // TODO: Evaluate if we want to hardcode things here instead.
  FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"1:123:ios:123abc"
                                                    GCMSenderID:@"correct_gcm_sender_id"];
  options.APIKey = @"correct_api_key";
  options.projectID = @"abc-xyz-123";
  return options;
}

#pragma mark - Set Up.

- (void)testURLScheme_NoApiKey {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
  BOOL setUpSucceed = [self.service setUpWithLaunchOptions:nil
                                                    apiKey:nil
                                                 urlScheme:nil
                                              userDefaults:nil];
#pragma clang diagnostic pop
  XCTAssertFalse(setUpSucceed, @"Should fail when apiKey is nil.");
}

- (void)testURLScheme_MinimumParameters {
  BOOL setUpSucceed = [self.service setUpWithLaunchOptions:nil
                                                    apiKey:kAPIKey
                                                 urlScheme:nil
                                              userDefaults:nil];
  XCTAssertTrue(setUpSucceed, @"Should not fail when apiKey is set.");
}

- (void)testFactoryMethodReturnsProperClassObject {
  id service = [FIRDynamicLinks dynamicLinks];

  XCTAssertNotNil(service, @"Factory method returned nil");
  XCTAssertEqualObjects([service class], [FIRDynamicLinks class],
                        @"Factory returned incorrect class object");
}

- (void)testURLScheme_LaunchOptionsWithCustomSchemeURL {
  NSString *deepLinkString =
      [NSString stringWithFormat:kStructuredLinkFmtDeepLink,
                                 [[NSBundle mainBundle] bundleIdentifier], @"abc123"];
  NSDictionary *launchOptions =
      @{UIApplicationLaunchOptionsURLKey : [NSURL URLWithString:deepLinkString]};

  [self.userDefaults setBool:NO forKey:kFIRDLReadDeepLinkAfterInstallKey];
  [self.service setUpWithLaunchOptions:launchOptions
                                apiKey:kAPIKey
                             urlScheme:nil
                          userDefaults:self.userDefaults];
  XCTAssertTrue([self.userDefaults boolForKey:kFIRDLReadDeepLinkAfterInstallKey]);
}

- (void)testURLScheme_LaunchOptionsWithUniversalLinkURL {
  NSString *deepLinkString =
      [NSString stringWithFormat:kStructuredUniversalLinkFmtDeepLink, @"abc123"];
  NSDictionary *launchOptions =
      @{UIApplicationLaunchOptionsURLKey : [NSURL URLWithString:deepLinkString]};

  [self.userDefaults setBool:NO forKey:kFIRDLReadDeepLinkAfterInstallKey];
  [self.service setUpWithLaunchOptions:launchOptions
                                apiKey:kAPIKey
                             urlScheme:nil
                          userDefaults:self.userDefaults];
  XCTAssertTrue([self.userDefaults boolForKey:kFIRDLReadDeepLinkAfterInstallKey]);
}

- (void)testURLScheme_LaunchOptionsWithInvalidURLWillNotResetUserDefaultsFlag {
  NSDictionary *launchOptions =
      @{UIApplicationLaunchOptionsURLKey : [NSURL URLWithString:@"https://www.google.com"]};

  [self.userDefaults setBool:YES forKey:kFIRDLReadDeepLinkAfterInstallKey];
  [self.service setUpWithLaunchOptions:launchOptions
                                apiKey:kAPIKey
                             urlScheme:nil
                          userDefaults:self.userDefaults];
  XCTAssertTrue([self.userDefaults boolForKey:kFIRDLReadDeepLinkAfterInstallKey]);
}

- (void)testURLScheme_Nil {
  BOOL setUpSucceed = [self.service setUpWithLaunchOptions:nil
                                                    apiKey:kAPIKey
                                                 urlScheme:nil
                                              userDefaults:nil];
  XCTAssertTrue(setUpSucceed);
  XCTAssertEqualObjects(self.service.URLScheme, [NSBundle mainBundle].bundleIdentifier);
}

- (void)testURLScheme_EmptyString {
  BOOL setUpSucceed = [self.service setUpWithLaunchOptions:nil
                                                    apiKey:kAPIKey
                                                 urlScheme:@""
                                              userDefaults:nil];
  XCTAssertTrue(setUpSucceed);
  XCTAssertEqualObjects(self.service.URLScheme, [NSBundle mainBundle].bundleIdentifier);
}

- (void)testURLScheme_NonNil {
  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];
  XCTAssertEqualObjects(self.service.URLScheme, kURLScheme,
                        @"URLScheme should be the same as in argument");
}

// TODO (b/63079414) Re-enable this
- (void)DISABLED_testConfigNamedFIRApp {
  [self removeAllFIRApps];

  id deepLinkServicePartialMock = OCMPartialMock([FIRDynamicLinks dynamicLinks]);
  [[deepLinkServicePartialMock reject] configureDynamicLinks:[OCMArg any]];
  [FIRApp configureWithName:@"NonDefaultName" options:[FIROptions defaultOptions]];
  [deepLinkServicePartialMock stopMocking];
}

// TODO (b/37855379) re-enable the test
- (void)DISABLED_testConfigForFIRApp {
  [self removeAllFIRApps];

  id deepLinkServicePartialMock = OCMPartialMock([FIRDynamicLinks dynamicLinks]);
  [FIRApp configure];
  OCMVerify([deepLinkServicePartialMock configureDynamicLinks:[OCMArg any]]);
  [deepLinkServicePartialMock stopMocking];
}

#pragma mark - dynamicLinkFromCustomSchemeURL

- (void)testCustomScheme_NoDeepLink {
  NSString *urlString =
      [NSString stringWithFormat:kStructuredLinkFmtFreeform, kURLScheme, @"abc123xyz"];
  NSURL *url = [NSURL URLWithString:urlString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];
  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  XCTAssertNil(dynamicLink, @"invite should be nil since there is no parameter.");
}

- (void)testCustomScheme_DeepLinkOnly {
  NSString *deepLinkString = @"https://developers.google.com/products/";
  NSString *urlString =
      [NSString stringWithFormat:kStructuredLinkFmtDeepLink, kURLScheme, deepLinkString];
  NSURL *url = [NSURL URLWithString:urlString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];
  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  XCTAssertEqualObjects(dynamicLink.url.absoluteString, deepLinkString);
  XCTAssertNil(dynamicLink.inviteId);
}

- (void)testCustomScheme_InvitationOnly {
  NSString *invitationId = @"213920940217491274389172947";

  NSString *urlString =
      [NSString stringWithFormat:kStructuredLinkFmtInvitation, kURLScheme, invitationId];
  NSURL *url = [NSURL URLWithString:urlString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];
  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  XCTAssertNil(dynamicLink.url);
  XCTAssertEqualObjects(dynamicLink.inviteId, invitationId);
}

- (void)testCustomScheme_DeepLinkAndInvitation {
  NSString *deepLinkString = @"https://developers.google.com/products/";
  NSString *invitationId = @"21392094021749127-4389172947";

  NSString *urlString = [NSString stringWithFormat:kStructuredLinkFmtDeepLinkAndInvitation,
                                                   [[NSBundle mainBundle] bundleIdentifier],
                                                   deepLinkString, invitationId];
  NSURL *url = [NSURL URLWithString:urlString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];
  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  XCTAssertEqualObjects(dynamicLink.url.absoluteString, deepLinkString);
  XCTAssertEqualObjects(dynamicLink.inviteId, invitationId);
}

- (void)testCustomScheme_FirstTimeOpenedWithCustomSchemeShouldGetStrongMatch {
  NSString *invitationId = @"21392094021749127-4389172947";

  NSString *urlString =
      [NSString stringWithFormat:kStructuredLinkFmtInvitation, kURLScheme, invitationId];
  NSURL *url = [NSURL URLWithString:urlString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];
  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  XCTAssertEqual(dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceStrong,
                 @"matchConfidence should be strong when app is first opened with custom scheme.");
  XCTAssertNil(dynamicLink.url);
  XCTAssertEqualObjects(dynamicLink.inviteId, invitationId);
}

- (void)testCustomScheme_FirstTimeOpenedFromDeviceHeuristicsCodepathShouldGetWeakMatch {
  NSString *invitationId = @"21392094021749127-4389172947";

  NSString *urlString =
      [NSString stringWithFormat:kStructuredLinkFmtInvitationWeak, kURLScheme, invitationId];
  NSURL *url = [NSURL URLWithString:urlString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];
  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  XCTAssertEqual(
      dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceWeak,
      @"matchConfidence should be weak when app is first opened from device heuristics codepath.");
  XCTAssertNil(dynamicLink.url);
  XCTAssertEqualObjects(dynamicLink.inviteId, invitationId);
}

- (void)testCustomScheme_StrongMatch {
  NSString *invitationId = @"21392094021749127-4389172947";

  NSString *urlString =
      [NSString stringWithFormat:kStructuredLinkFmtInvitation, kURLScheme, invitationId];
  NSURL *url = [NSURL URLWithString:urlString];

  // Simulate opening the app.
  [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kFIRDLReadDeepLinkAfterInstallKey];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];
  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  XCTAssertEqual(dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceStrong,
                 @"matchConfidence should be strong when opening an url after app is installed.");
  XCTAssertNil(dynamicLink.url);
  XCTAssertEqualObjects(dynamicLink.inviteId, invitationId);
}

- (void)testLinkParamWithPlus {
  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];
  NSString *durableDeepLinkString =
      [NSString stringWithFormat:@"gindeeplinkurl://google/link?deep_link_id=%@",
                                 kEncodedComplicatedURLString];
  NSURL *durabledeepLinkURL = [NSURL URLWithString:durableDeepLinkString];

  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:durabledeepLinkURL];

  NSString *deepLinkURLString = dynamicLink.url.absoluteString;

  XCTAssertEqualObjects(kDecodedComplicatedURLString, deepLinkURLString,
                        @"ddl url parameter and deep link url should be the same");
}

#pragma mark - dynamicLinkFromUniversalLinkURL

- (void)testUniversalLink_NoDeepLink {
  NSString *webPageURLString =
      [NSString stringWithFormat:kStructuredUniversalLinkFmtFreeForm, @"/abc123"];
  NSURL *url = [NSURL URLWithString:webPageURLString];

  NSUserActivity *activity =
      [[NSUserActivity alloc] initWithActivityType:NSUserActivityTypeBrowsingWeb];
  activity.webpageURL = [NSURL URLWithString:webPageURLString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];
  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  XCTAssertNil(dynamicLink, @"invite should be nil since there is no parameter.");
}

// Custom domain entries in plist file:
//  https://google.com
//  https://google.com/one
//  https://a.firebase.com/mypath
- (void)testDynamicLinkFromUniversalLinkURLWithCustomDomainLink {
  self.service = [[FIRDynamicLinks alloc] init];
  NSString *durableDeepLinkString = @"https://a.firebase.com/mypath/?link=http://abcd";
  NSURL *durabledeepLinkURL = [NSURL URLWithString:durableDeepLinkString];

  SwizzleDynamicLinkNetworkingWithMock();

  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:durabledeepLinkURL];

  XCTAssertNotNil(dynamicLink);
  NSString *deepLinkURLString = dynamicLink.url.absoluteString;

  XCTAssertEqualObjects(@"http://abcd", deepLinkURLString,
                        @"ddl url parameter and deep link url should be the same");
  UnswizzleDynamicLinkNetworking();
}

- (void)testDynamicLinkFromUniversalLinkURLCompletionWithCustomDomainLink {
  self.service = [[FIRDynamicLinks alloc] init];
  NSString *durableDeepLinkString = @"https://a.firebase.com/mypath/?link=http://abcd";
  NSURL *durabledeepLinkURL = [NSURL URLWithString:durableDeepLinkString];

  SwizzleDynamicLinkNetworkingWithMock();

  XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  [self.service
      dynamicLinkFromUniversalLinkURL:durabledeepLinkURL
                           completion:^(FIRDynamicLink *_Nullable dynamicLink,
                                        NSError *_Nullable error) {
                             XCTAssertTrue([NSThread isMainThread]);
                             XCTAssertNotNil(dynamicLink);
                             NSString *deepLinkURLString = dynamicLink.url.absoluteString;

                             XCTAssertEqualObjects(
                                 @"http://abcd", deepLinkURLString,
                                 @"ddl url parameter and deep link url should be the same");
                             [expectation fulfill];
                           }];
  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];

  UnswizzleDynamicLinkNetworking();
}

- (void)testDynamicLinkFromUniversalLinkURLWithSpecialCharacters {
  NSString *durableDeepLinkString =
      [NSString stringWithFormat:@"https://xyz.page.link/?link=%@", kEncodedComplicatedURLString];
  NSURL *durabledeepLinkURL = [NSURL URLWithString:durableDeepLinkString];

  SwizzleDynamicLinkNetworkingWithMock();

  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:durabledeepLinkURL];

  NSString *deepLinkURLString = dynamicLink.url.absoluteString;

  XCTAssertEqualObjects(kDecodedComplicatedURLString, deepLinkURLString,
                        @"ddl url parameter and deep link url should be the same");
  UnswizzleDynamicLinkNetworking();
}

- (void)testDynamicLinkFromUniversalLinkURLCompletionWithSpecialCharacters {
  NSString *durableDeepLinkString =
      [NSString stringWithFormat:@"https://xyz.page.link/?link=%@", kEncodedComplicatedURLString];
  NSURL *durabledeepLinkURL = [NSURL URLWithString:durableDeepLinkString];

  SwizzleDynamicLinkNetworkingWithMock();

  XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  [self.service
      dynamicLinkFromUniversalLinkURL:durabledeepLinkURL
                           completion:^(FIRDynamicLink *_Nullable dynamicLink,
                                        NSError *_Nullable error) {
                             XCTAssertTrue([NSThread isMainThread]);
                             NSString *deepLinkURLString = dynamicLink.url.absoluteString;

                             XCTAssertEqualObjects(
                                 kDecodedComplicatedURLString, deepLinkURLString,
                                 @"ddl url parameter and deep link url should be the same");
                             [expectation fulfill];
                           }];
  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];

  UnswizzleDynamicLinkNetworking();
}

- (void)testDynamicLinkFromUniversalLinkURLWithEncodedCharacters {
  NSString *durableDeepLinkString =
      [NSString stringWithFormat:@"https://xyz.page.link/?link=%@", kEncodedComplicatedURLString];
  NSURL *durabledeepLinkURL = [NSURL URLWithString:durableDeepLinkString];

  SwizzleDynamicLinkNetworkingWithMock();

  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:durabledeepLinkURL];

  NSString *deepLinkURLString = dynamicLink.url.absoluteString;

  XCTAssertEqualObjects(kDecodedComplicatedURLString, deepLinkURLString,
                        @"ddl url parameter and deep link url should be the same");
  UnswizzleDynamicLinkNetworking();
}

- (void)testDynamicLinkFromUniversalLinkURLCompletionWithEncodedCharacters {
  NSString *durableDeepLinkString =
      [NSString stringWithFormat:@"https://xyz.page.link/?link=%@", kEncodedComplicatedURLString];
  NSURL *durabledeepLinkURL = [NSURL URLWithString:durableDeepLinkString];

  SwizzleDynamicLinkNetworkingWithMock();

  XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  [self.service
      dynamicLinkFromUniversalLinkURL:durabledeepLinkURL
                           completion:^(FIRDynamicLink *_Nullable dynamicLink,
                                        NSError *_Nullable error) {
                             XCTAssertTrue([NSThread isMainThread]);
                             NSString *deepLinkURLString = dynamicLink.url.absoluteString;

                             XCTAssertEqualObjects(
                                 kDecodedComplicatedURLString, deepLinkURLString,
                                 @"ddl url parameter and deep link url should be the same");
                             [expectation fulfill];
                           }];
  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];

  UnswizzleDynamicLinkNetworking();
}

- (void)testUniversalLink_DeepLink {
  NSString *deepLinkString = @"https://www.google.com/maps/place/Minneapolis";
  NSString *webPageURLString =
      [NSString stringWithFormat:kStructuredUniversalLinkFmtDeepLink, deepLinkString];
  NSURL *url = [NSURL URLWithString:webPageURLString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  SwizzleDynamicLinkNetworkingWithMock();

  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:url];

  XCTAssertEqual(dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceStrong);
  XCTAssertEqualObjects(dynamicLink.url.absoluteString, deepLinkString);
  UnswizzleDynamicLinkNetworking();
}

- (void)testUniversalLinkWithCompletion_DeepLink {
  NSString *deepLinkString = @"https://www.google.com/maps/place/Minneapolis";
  NSString *webPageURLString =
      [NSString stringWithFormat:kStructuredUniversalLinkFmtDeepLink, deepLinkString];
  NSURL *url = [NSURL URLWithString:webPageURLString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  SwizzleDynamicLinkNetworkingWithMock();

  XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  [self.service
      dynamicLinkFromUniversalLinkURL:url
                           completion:^(FIRDynamicLink *_Nullable dynamicLink,
                                        NSError *_Nullable error) {
                             XCTAssertTrue([NSThread isMainThread]);
                             XCTAssertEqual(dynamicLink.matchConfidence,
                                            FIRDynamicLinkMatchConfidenceStrong);
                             XCTAssertEqualObjects(dynamicLink.url.absoluteString, deepLinkString);
                             [expectation fulfill];
                           }];
  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];

  UnswizzleDynamicLinkNetworking();
}

- (void)testUniversalLink_DeepLinkWithParameters {
  NSString *deepLinkString = @"https://www.google.com?key1%3Dvalue1%26key2%3Dvalue2";
  NSString *parsedDeepLinkString = @"https://www.google.com?key1=value1&key2=value2";
  NSString *webPageURLString =
      [NSString stringWithFormat:kStructuredUniversalLinkFmtDeepLink, deepLinkString];
  NSURL *url = [NSURL URLWithString:webPageURLString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  SwizzleDynamicLinkNetworkingWithMock();

  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:url];
  XCTAssertEqual(dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceStrong);
  XCTAssertEqualObjects(dynamicLink.url.absoluteString, parsedDeepLinkString);
  UnswizzleDynamicLinkNetworking();
}

- (void)testUniversalLinkWithCompletion_DeepLinkWithParameters {
  NSString *deepLinkString = @"https://www.google.com?key1%3Dvalue1%26key2%3Dvalue2";
  NSString *parsedDeepLinkString = @"https://www.google.com?key1=value1&key2=value2";
  NSString *webPageURLString =
      [NSString stringWithFormat:kStructuredUniversalLinkFmtDeepLink, deepLinkString];
  NSURL *url = [NSURL URLWithString:webPageURLString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  SwizzleDynamicLinkNetworkingWithMock();

  XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  [self.service dynamicLinkFromUniversalLinkURL:url
                                     completion:^(FIRDynamicLink *_Nullable dynamicLink,
                                                  NSError *_Nullable error) {
                                       XCTAssertTrue([NSThread isMainThread]);
                                       XCTAssertEqual(dynamicLink.matchConfidence,
                                                      FIRDynamicLinkMatchConfidenceStrong);
                                       XCTAssertEqualObjects(dynamicLink.url.absoluteString,
                                                             parsedDeepLinkString);
                                       [expectation fulfill];
                                     }];
  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
  UnswizzleDynamicLinkNetworking();
}

- (void)testResolveLinkReturnsDLWithNilMinAppVersionWhenNotPresent {
  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  NSString *urlString = @"http://reinl.page.link/t4ionvr";
  NSURL *url = [NSURL URLWithString:urlString];

  void (^executeRequestBlock)(id, NSDictionary *, NSString *, FIRNetworkRequestCompletionHandler) =
      ^(id p1, NSDictionary *requestBody, NSString *requestURLString,
        FIRNetworkRequestCompletionHandler handler) {
        NSDictionary *dictionary = @{kFDLResolvedLinkDeepLinkURLKey : kEncodedComplicatedURLString};
        NSData *data = FIRDataWithDictionary(dictionary, nil);

        handler(data, nil, nil);
      };

  SEL executeRequestSelector = @selector(executeOnePlatformRequest:forURL:completionHandler:);
  [GULSwizzler swizzleClass:[FIRDynamicLinkNetworking class]
                   selector:executeRequestSelector
            isClassSelector:NO
                  withBlock:executeRequestBlock];

  XCTestExpectation *expectation = [self expectationWithDescription:@"handler called"];

  [self.service
      handleUniversalLink:url
               completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
                 XCTAssertNil(dynamicLink.minimumAppVersion,
                              @"Min app version not nil when not present.");
                 [expectation fulfill];
               }];

  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
}

- (void)testResolveLinkReturnsDLWithMinAppVersionWhenPresent {
  NSString *expectedMinVersion = @"8g5u3e";

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  NSString *urlString = @"http://reinl.page.link/t4ionvr";
  NSURL *url = [NSURL URLWithString:urlString];

  void (^executeRequestBlock)(id, NSDictionary *, NSString *, FIRNetworkRequestCompletionHandler) =
      ^(id p1, NSDictionary *requestBody, NSString *requestURLString,
        FIRNetworkRequestCompletionHandler handler) {
        NSDictionary *dictionary = @{
          kFDLResolvedLinkDeepLinkURLKey : kEncodedComplicatedURLString,
          kFDLResolvedLinkMinAppVersionKey : expectedMinVersion,
        };
        NSData *data = FIRDataWithDictionary(dictionary, nil);
        NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url
                                                                  statusCode:200
                                                                 HTTPVersion:nil
                                                                headerFields:nil];
        handler(data, response, nil);
      };

  SEL executeRequestSelector = @selector(executeOnePlatformRequest:forURL:completionHandler:);
  [GULSwizzler swizzleClass:[FIRDynamicLinkNetworking class]
                   selector:executeRequestSelector
            isClassSelector:NO
                  withBlock:executeRequestBlock];

  XCTestExpectation *expectation = [self expectationWithDescription:@"handler called"];

  [self.service
      handleUniversalLink:url
               completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
                 XCTAssertEqualObjects(expectedMinVersion, dynamicLink.minimumAppVersion,
                                       @"min app version did not match imv parameter.");
                 [expectation fulfill];
               }];

  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
}

- (void)testUniversalLinkWithSubdomain_NoDeepLink {
  NSString *webPageURLString =
      [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomain, @"/abc123"];
  NSURL *url = [NSURL URLWithString:webPageURLString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];
  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  XCTAssertNil(dynamicLink, @"deepLink should be nil since there is no parameter.");
}

- (void)testDynamicLinkFromCustomSchemeURLReturnsDLWithNilMinimumVersion {
  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];
  NSURL *url = FIRDLDeepLinkURLWithInviteID(nil, kEncodedComplicatedURLString, nil, nil, nil, nil,
                                            nil, NO, nil, nil, kURLScheme, nil);
  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];

  XCTAssertNil(dynamicLink.minimumAppVersion, @"Min app version was not nil when not set.");
}

- (void)testDynamicLinkFromCustomSchemeURLReturnsDLMinimumVersion {
  NSString *expectedMinVersion = @"03-9g03hfd";

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];
  NSURL *url = FIRDLDeepLinkURLWithInviteID(nil, kEncodedComplicatedURLString, nil, nil, nil, nil,
                                            nil, NO, nil, expectedMinVersion, kURLScheme, nil);
  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];

  NSString *minVersion = dynamicLink.minimumAppVersion;

  XCTAssertEqualObjects(expectedMinVersion, minVersion,
                        @"Min version didn't match the min app version parameter");
}

- (void)testDynamicLinkFromUniversalLinkURLReturnsDLWithNilMinimumVersion {
  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  NSURL *url = FIRDLDeepLinkURLWithInviteID(nil, kEncodedComplicatedURLString, nil, nil, nil, nil,
                                            nil, NO, nil, nil, kURLScheme, nil);

  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:url];

  NSString *minVersion = dynamicLink.minimumAppVersion;

  XCTAssertNil(minVersion, @"Min app version was not nil when not set.");
}

- (void)testDynamicLinkFromUniversalLinkURLCompletionReturnsDLWithNilMinimumVersion {
  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  NSURL *url = FIRDLDeepLinkURLWithInviteID(nil, kEncodedComplicatedURLString, nil, nil, nil, nil,
                                            nil, NO, nil, nil, kURLScheme, nil);

  XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  [self.service
      dynamicLinkFromUniversalLinkURL:url
                           completion:^(FIRDynamicLink *_Nullable dynamicLink,
                                        NSError *_Nullable error) {
                             XCTAssertTrue([NSThread isMainThread]);
                             NSString *minVersion = dynamicLink.minimumAppVersion;

                             XCTAssertNil(minVersion, @"Min app version was not nil when not set.");
                             [expectation fulfill];
                           }];
  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
}

- (void)testDynamicLinkFromUniversalLinkURLReturnsDLMinimumVersion {
  NSString *expectedMinVersion = @"03-9g03hfd";
  NSString *urlSuffix =
      [NSString stringWithFormat:@"%@&imv=%@", kEncodedComplicatedURLString, expectedMinVersion];
  NSString *urlString =
      [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, urlSuffix];
  NSURL *url = [NSURL URLWithString:urlString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:url];

  NSString *minVersion = dynamicLink.minimumAppVersion;

  XCTAssertEqualObjects(expectedMinVersion, minVersion, @"Min version didn't match imv= parameter");
}

- (void)testDynamicLinkFromUniversalLinkURLReturnsUTMParams {
  NSString *expectedUtmSource = @"utm_source";
  NSString *expectedUtmMedium = @"utm_medium";
  NSString *expectedUtmCampaign = @"utm_campaign";
  NSString *expectedUtmTerm = @"utm_term";
  NSString *expectedUtmContent = @"utm_content";

  NSString *utmParamsString = [NSString
      stringWithFormat:@"utm_source=%@&utm_medium=%@&utm_campaign=%@&utm_term=%@&utm_content=%@",
                       expectedUtmSource, expectedUtmMedium, expectedUtmCampaign, expectedUtmTerm,
                       expectedUtmContent];
  NSString *urlSuffix =
      [NSString stringWithFormat:@"%@&%@", kEncodedComplicatedURLString, utmParamsString];

  NSString *urlString =
      [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, urlSuffix];
  NSURL *url = [NSURL URLWithString:urlString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];
  XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  [self.service
      dynamicLinkFromUniversalLinkURL:url
                           completion:^(FIRDynamicLink *_Nullable dynamicLink,
                                        NSError *_Nullable error) {
                             XCTAssertTrue([NSThread isMainThread]);
                             NSDictionary *utmParameters = dynamicLink.utmParametersDictionary;
                             NSString *utmSource = [utmParameters objectForKey:@"utm_source"];
                             XCTAssertEqualObjects(utmSource, expectedUtmSource,
                                                   @"UtmSource doesn't match utm_source parameter");

                             NSString *utmMedium = [utmParameters objectForKey:@"utm_medium"];
                             XCTAssertEqualObjects(utmMedium, expectedUtmMedium,
                                                   @"UtmMedium doesn't match utm_medium parameter");

                             NSString *utmCampaign = [utmParameters objectForKey:@"utm_campaign"];
                             XCTAssertEqualObjects(
                                 utmCampaign, expectedUtmCampaign,
                                 @"UtmCampaign doesn't match utm_campaign parameter");

                             NSString *utmTerm = [utmParameters objectForKey:@"utm_term"];
                             XCTAssertEqualObjects(utmTerm, expectedUtmTerm,
                                                   @"UtmTerm doesn't match utm_term parameter");

                             NSString *utmContent = [utmParameters objectForKey:@"utm_content"];
                             XCTAssertEqualObjects(
                                 utmContent, expectedUtmContent,
                                 @"UtmContent doesn't match utm_content parameter");

                             [expectation fulfill];
                           }];

  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
}

- (void)testDynamicLinkFromUniversalLinkURLCompletionReturnsDLMinimumVersion {
  NSString *expectedMinVersion = @"03-9g03hfd";
  NSString *urlSuffix =
      [NSString stringWithFormat:@"%@&imv=%@", kEncodedComplicatedURLString, expectedMinVersion];
  NSString *urlString =
      [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, urlSuffix];
  NSURL *url = [NSURL URLWithString:urlString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  [self.service
      dynamicLinkFromUniversalLinkURL:url
                           completion:^(FIRDynamicLink *_Nullable dynamicLink,
                                        NSError *_Nullable error) {
                             XCTAssertTrue([NSThread isMainThread]);
                             NSString *minVersion = dynamicLink.minimumAppVersion;

                             XCTAssertEqualObjects(expectedMinVersion, minVersion,
                                                   @"Min version didn't match imv= parameter");
                             [expectation fulfill];
                           }];
  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
}

- (void)testUniversalLinkWithSubdomain_DeepLink {
  NSString *deepLinkString = @"https://www.google.com/maps/place/Minneapolis";
  NSString *webPageURLString =
      [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, deepLinkString];
  NSURL *url = [NSURL URLWithString:webPageURLString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  SwizzleDynamicLinkNetworkingWithMock();

  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:url];
  XCTAssertEqual(dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceStrong);
  XCTAssertEqualObjects(dynamicLink.url.absoluteString, deepLinkString);
  UnswizzleDynamicLinkNetworking();
}

- (void)testUniversalLinkWithCompletionWithSubdomain_DeepLink {
  NSString *deepLinkString = @"https://www.google.com/maps/place/Minneapolis";
  NSString *webPageURLString =
      [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, deepLinkString];
  NSURL *url = [NSURL URLWithString:webPageURLString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  SwizzleDynamicLinkNetworkingWithMock();

  XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  [self.service
      dynamicLinkFromUniversalLinkURL:url
                           completion:^(FIRDynamicLink *_Nullable dynamicLink,
                                        NSError *_Nullable error) {
                             XCTAssertTrue([NSThread isMainThread]);
                             XCTAssertEqual(dynamicLink.matchConfidence,
                                            FIRDynamicLinkMatchConfidenceStrong);
                             XCTAssertEqualObjects(dynamicLink.url.absoluteString, deepLinkString);
                             [expectation fulfill];
                           }];
  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];

  UnswizzleDynamicLinkNetworking();
}

- (void)testUniversalLinkWithSubdomain_DeepLinkWithParameters {
  NSString *deepLinkString = @"https://www.google.com?key1%3Dvalue1%26key2%3Dvalue2";
  NSString *parsedDeepLinkString = @"https://www.google.com?key1=value1&key2=value2";
  NSString *webPageURLString =
      [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, deepLinkString];
  NSURL *url = [NSURL URLWithString:webPageURLString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:url];
  XCTAssertEqual(dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceStrong);
  XCTAssertEqualObjects(dynamicLink.url.absoluteString, parsedDeepLinkString);
}

- (void)testUniversalLinkWithCompletionWithSubdomain_DeepLinkWithParameters {
  NSString *deepLinkString = @"https://www.google.com?key1%3Dvalue1%26key2%3Dvalue2";
  NSString *parsedDeepLinkString = @"https://www.google.com?key1=value1&key2=value2";
  NSString *webPageURLString =
      [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, deepLinkString];
  NSURL *url = [NSURL URLWithString:webPageURLString];

  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  [self.service dynamicLinkFromUniversalLinkURL:url
                                     completion:^(FIRDynamicLink *_Nullable dynamicLink,
                                                  NSError *_Nullable error) {
                                       XCTAssertTrue([NSThread isMainThread]);
                                       XCTAssertEqual(dynamicLink.matchConfidence,
                                                      FIRDynamicLinkMatchConfidenceStrong);
                                       XCTAssertEqualObjects(dynamicLink.url.absoluteString,
                                                             parsedDeepLinkString);
                                       [expectation fulfill];
                                     }];
  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
}

- (void)testResolveLinkRespectsResponseSuccessStatusCode {
  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  NSString *urlString = @"http://domain";
  NSURL *url = [NSURL URLWithString:urlString];

  void (^executeRequestBlock)(id, NSDictionary *, NSString *, FIRNetworkRequestCompletionHandler) =
      ^(id p1, NSDictionary *requestBody, NSString *requestURLString,
        FIRNetworkRequestCompletionHandler handler) {
        NSData *data = FIRDataWithDictionary(@{}, nil);
        NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url
                                                                  statusCode:200
                                                                 HTTPVersion:nil
                                                                headerFields:nil];
        handler(data, response, nil);
      };

  SEL executeRequestSelector = @selector(executeOnePlatformRequest:forURL:completionHandler:);
  [GULSwizzler swizzleClass:[FIRDynamicLinkNetworking class]
                   selector:executeRequestSelector
            isClassSelector:NO
                  withBlock:executeRequestBlock];

  XCTestExpectation *expectation = [self expectationWithDescription:@"handler called"];

  [self.service resolveShortLink:url
                      completion:^(NSURL *_Nullable url, NSError *_Nullable error) {
                        XCTAssertNotNil(url);
                        XCTAssertNil(error);
                        [expectation fulfill];
                      }];

  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
}

- (void)testResolveLinkRespectsResponseErrorStatusCode {
  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  NSString *urlString = @"http://domain";
  NSURL *url = [NSURL URLWithString:urlString];

  NSError *expectedError = [NSError
      errorWithDomain:@"com.firebase.dynamicLinks"
                 code:0
             userInfo:@{
               @"message" : [NSString stringWithFormat:@"Failed to resolve link: %@", urlString]
             }];

  void (^executeRequestBlock)(id, NSDictionary *, NSString *, FIRNetworkRequestCompletionHandler) =
      ^(id p1, NSDictionary *requestBody, NSString *requestURLString,
        FIRNetworkRequestCompletionHandler handler) {
        NSData *data = FIRDataWithDictionary(@{}, nil);
        NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url
                                                                  statusCode:400
                                                                 HTTPVersion:nil
                                                                headerFields:nil];
        handler(data, response, nil);
      };

  SEL executeRequestSelector = @selector(executeOnePlatformRequest:forURL:completionHandler:);
  [GULSwizzler swizzleClass:[FIRDynamicLinkNetworking class]
                   selector:executeRequestSelector
            isClassSelector:NO
                  withBlock:executeRequestBlock];

  XCTestExpectation *expectation = [self expectationWithDescription:@"handler called"];

  [self.service resolveShortLink:url
                      completion:^(NSURL *_Nullable url, NSError *_Nullable error) {
                        XCTAssertNil(url);
                        XCTAssertNotNil(error);
                        XCTAssertEqualObjects(error, expectedError,
                                              @"Handle universal link returned unexpected error");
                        [expectation fulfill];
                      }];

  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
}

- (void)testPassMatchesShortLinkFormatForDDLDomains {
  NSArray<NSString *> *urlStrings = @[
    @"https://someapp.app.goo.gl/somepath",
    @"https://someapp.app.goo.gl/link",
    @"https://someapp.app.goo.gl/somepath?link=https://somedomain",
    @"https://someapp.app.goo.gl/somepath?somekey=somevalue",
    @"https://someapp.app.goo.gl/somepath/?link=https://somedomain",
    @"https://someapp.app.goo.gl/somepath/?somekey=somevalue",
    @"https://someapp.app.google/somepath",
    @"https://someapp.app.google/link",
    @"https://someapp.app.google/somepath?link=https://somedomain",
    @"https://someapp.app.google/somepath?somekey=somevalue",
    @"https://someapp.app.google/somepath/?link=https://somedomain",
    @"https://someapp.app.google/somepath/?somekey=somevalue",
    @"https://someapp.page.link/somepath",
    @"https://someapp.page.link/link",
    @"https://someapp.page.link/somepath?link=https://somedomain",
    @"https://someapp.page.link/somepath?somekey=somevalue",
    @"https://someapp.page.link/somepath/?link=https://somedomain",
    @"https://someapp.page.link/somepath/?somekey=somevalue",
    @"http://someapp.page.link/somepath",
    @"http://someapp.page.link/link",
    @"http://someapp.page.link/somepath?link=https://somedomain",
    @"http://someapp.page.link/somepath?somekey=somevalue",
    @"http://someapp.page.link/somepath/?link=http://somedomain",
    @"http://someapp.page.link/somepath/?somekey=somevalue"
  ];

  for (NSString *urlString in urlStrings) {
    NSURL *url = [NSURL URLWithString:urlString];
    BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];

    XCTAssertTrue(matchesShortLinkFormat,
                  @"Non-DDL domain URL matched short link format with URL: %@", url);
  }
}

- (void)testFailMatchesShortLinkFormat {
  NSArray<NSString *> *urlStrings = @[
    @"https://someapp.app.goo.gl",
    @"https://someapp.app.goo.gl/",
    @"https://someapp.app.goo.gl?",
    @"https://someapp.app.goo.gl/?",
    @"https://someapp.app.goo.gl?somekey=somevalue",
    @"https://someapp.app.goo.gl/?somekey=somevalue",
    @"https://someapp.app.goo.gl/somepath/somepath2",
    @"https://someapp.app.goo.gl/somepath/somepath2?somekey=somevalue",
    @"https://someapp.app.goo.gl/somepath/somepath2?link=https://somedomain",
    @"https://someapp.app.google",
    @"https://someapp.app.google/",
    @"https://someapp.app.google?",
    @"https://someapp.app.google/?",
    @"https://someapp.app.google?somekey=somevalue",
    @"https://someapp.app.google/?somekey=somevalue",
    @"https://someapp.app.google/somepath/somepath2",
    @"https://someapp.app.google/somepath/somepath2?somekey=somevalue",
    @"https://someapp.app.google/somepath/somepath2?link=https://somedomain",
    @"https://someapp.page.link",
    @"https://someapp.page.link/",
    @"https://someapp.page.link?",
    @"https://someapp.page.link/?",
    @"https://someapp.page.link?somekey=somevalue",
    @"https://someapp.page.link/?somekey=somevalue",
    @"https://someapp.page.link/somepath/somepath2",
    @"https://someapp.page.link/somepath/somepath2?somekey=somevalue",
    @"https://someapp.page.link/somepath/somepath2?link=https://somedomain",
    @"https://www.google.com/maps/place/@1,1/My+Home/",
    @"https://mydomain.com/t439gfde",
    @"https://goo.gl/309dht4",
    @"https://59eh.goo.gl/309dht4",
    @"https://app.59eh.goo.gl/309dht4",
    @"https://goo.gl/i/309dht4",
    @"https://page.link/i/309dht4",
    @"https://fjo3eh.goo.gl/i/309dht4",
    @"https://app.fjo3eh.goo.gl/i/309dht4",
    @"https://1234.page.link/link/dismiss"
  ];

  for (NSString *urlString in urlStrings) {
    NSURL *url = [NSURL URLWithString:urlString];
    BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];

    XCTAssertFalse(matchesShortLinkFormat,
                   @"Non-DDL domain URL matched short link format with URL: %@", url);
  }
}

// Custom domain entries in plist file:
//  https://google.com
//  https://google.com/one
//  https://a.firebase.com/mypath
- (void)testFailMatchesShortLinkFormatForCustomDomains {
  NSArray<NSString *> *urlStrings = @[
    @"https://google.com",
    @"https://a.firebase.com",
    @"https://google.com/",
    @"https://google.com?",
    @"https://google.com/?",
    @"https://google.com?utm_campgilink=someval",
    @"https://google.com?somekey=someval",
  ];

  for (NSString *urlString in urlStrings) {
    NSURL *url = [NSURL URLWithString:urlString];
    BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];

    XCTAssertFalse(matchesShortLinkFormat,
                   @"Non-DDL domain URL matched short link format with URL: %@", url);
  }
}

// Custom domain entries in plist file:
//  https://google.com
//  https://google.com/one
//  https://a.firebase.com/mypath
- (void)testPassMatchesShortLinkFormatForCustomDomains {
  NSArray<NSString *> *urlStrings = @[
    @"https://google.com/xyz", @"https://google.com/xyz/?link=https://somedomain",
    @"https://google.com/xyz?link=https://somedomain",
    @"https://google.com/xyz/?link=https://somedomain", @"https://google.com/one/xyz",
    @"https://google.com/one/xyz?link=https://somedomain",
    @"https://google.com/one/xyz/?link=https://somedomain",
    @"https://google.com/one?utm_campaignlink=https://somedomain",
    @"https://google.com/one/?utm_campaignlink=https://somedomain", @"https://google.com/mylink",
    @"https://google.com/one/mylink", @"https://a.firebase.com/mypath/mylink"
  ];

  for (NSString *urlString in urlStrings) {
    NSURL *url = [NSURL URLWithString:urlString];
    BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];

    XCTAssertTrue(matchesShortLinkFormat,
                  @"Non-DDL domain URL matched short link format with URL: %@", url);
  }
}

- (void)testMatchesUnversalLinkWithShortDurableLink {
  NSString *urlString = @"https://sample.page.link/79g49s";
  NSURL *url = [NSURL URLWithString:urlString];
  BOOL matchesShort = [self.service matchesShortLinkFormat:url];

  XCTAssertTrue(matchesShort, @"Short Durable Link didn't match short link");
}

- (void)testMatchesUnversalLinkWithAppInvite {
  NSString *urlString = @"https://sample.page.link/i/79g49s";
  NSURL *url = [NSURL URLWithString:urlString];
  BOOL matchesShort = [self.service matchesShortLinkFormat:url];

  XCTAssertTrue(matchesShort, @"AppInvite didn't match short link");
}

- (void)testDoesNotMatchesShortLinkFormatWithNonDDLDomains {
  NSArray<NSString *> *urlStrings = @[
    @"https://www.google.com/maps/place/@1,1/My+Home/", @"https://mydomain.com/t439gfde",
    @"https://goo.gl/309dht4", @"https://59eh.goo.gl/309dht4", @"https://app.59eh.goo.gl/309dht4",
    @"https://goo.gl/i/309dht4", @"https://page.link/i/309dht4", @"https://fjo3eh.goo.gl/i/309dht4",
    @"https://app.fjo3eh.goo.gl/i/309dht4", @"https://1234.page.link/link/dismiss"
  ];

  for (NSString *urlString in urlStrings) {
    NSURL *url = [NSURL URLWithString:urlString];
    BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];

    XCTAssertFalse(matchesShortLinkFormat,
                   @"Non-DDL domain URL matched short link format with URL: %@", url);
  }
}

- (void)testHandleUniversalLinkWithShortLink {
  NSString *shortLinkString = @"https://sample.page.link/549igo";

  NSString *bundleID = [NSBundle mainBundle].bundleIdentifier;

  NSString *customSchemeURLString = [NSString
      stringWithFormat:kStructuredLinkFmtDeepLink, bundleID, kEncodedComplicatedURLString];

  XCTestExpectation *handleLinkCompletionExpectation =
      [self expectationWithDescription:@"handleLink"];
  XCTestExpectation *linkResolverCompletionExpectation =
      [self expectationWithDescription:@"linkResolver"];

  FakeShortLinkResolver *resolver =
      [FakeShortLinkResolver resolverWithBlock:^NSURL *(NSURL *shortLink) {
        [linkResolverCompletionExpectation fulfill];
        return [NSURL URLWithString:customSchemeURLString];
      }];

  SwizzleDynamicLinkNetworking(resolver);

  [self.service
      handleUniversalLink:[NSURL URLWithString:shortLinkString]
               completion:^(FIRDynamicLink *_Nonnull dynamicLink, NSError *_Nullable error) {
                 NSString *returnedURLString = dynamicLink.url.absoluteString;
                 XCTAssertEqualObjects(kDecodedComplicatedURLString, returnedURLString,
                                       @"Handle universal link returned unexpected link");
                 [handleLinkCompletionExpectation fulfill];
               }];

  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
  UnswizzleDynamicLinkNetworking();
}

- (void)testHandleUniversalLinkWithLongLink {
  NSString *longLinkString = [NSString
      stringWithFormat:@"https://sample.page.link?link=%@&ibi=com.google.sample&ius=79306483",
                       kEncodedComplicatedURLString];

  XCTestExpectation *handleLinkCompletionExpectation =
      [self expectationWithDescription:@"handleLink"];
  __block NSUInteger resolverInvocationsCount = 0;

  // should not be used.
  FakeShortLinkResolver *resolver =
      [FakeShortLinkResolver resolverWithBlock:^NSURL *(NSURL *shortLink) {
        resolverInvocationsCount++;
        return [NSURL URLWithString:kDecodedComplicatedURLString];
      }];

  id handleUniversalLinkBlock = ^(FIRDynamicLink *_Nonnull dynamicLink, NSError *_Nullable error) {
    [handleLinkCompletionExpectation fulfill];
    NSURL *expectedResolvedLink = [NSURL URLWithString:kDecodedComplicatedURLString];
    XCTAssertEqualObjects(expectedResolvedLink, dynamicLink.url,
                          @"Resolve short link returned unexpected link");
  };

  SwizzleDynamicLinkNetworking(resolver);

  [self.service handleUniversalLink:[NSURL URLWithString:longLinkString]
                         completion:handleUniversalLinkBlock];

  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];

  // It is expected to call resolveLink once for logging.
  XCTAssertEqual(resolverInvocationsCount, 1,
                 @"handleUniversalLink called resolveLink more than once");
  UnswizzleDynamicLinkNetworking();
}

- (void)testHandleUniversalLinkCallsHandleUniversalLinkResolver {
  XCTestExpectation *handleLinkCompletionExpectation =
      [self expectationWithDescription:@"handleLink"];
  void (^replacementBlock)(void) = ^{
    [handleLinkCompletionExpectation fulfill];
  };

  SEL selectorToSwizzle = @selector(handleUniversalLink:completion:);

  [GULSwizzler swizzleClass:[FIRDynamicLinks class]
                   selector:selectorToSwizzle
            isClassSelector:NO
                  withBlock:replacementBlock];

  NSURL *url = [NSURL URLWithString:@"https://google.com"];

  [self.service
      handleUniversalLink:url
               completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error){
               }];

  [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];

  [GULSwizzler unswizzleClass:[FIRDynamicLinks class]
                     selector:selectorToSwizzle
              isClassSelector:NO];
}

- (void)testHandleUniversalLinkCompletionReturnsNoForNonDDL {
  NSArray<NSString *> *urlStrings = @[
    @"https://www.google.com/maps/place/@1,1/My+Home/", @"https://mydomain.com/t439gfde",
    @"https://goo.gl/309dht4", @"https://59eh.goo.gl/309dht4", @"https://app.59eh.goo.gl/309dht4",
    @"https://goo.gl/i/309dht4", @"https://page.link/i/309dht4", @"https://fjo3eh.goo.gl/i/309dht4",
    @"https://app.fjo3eh.goo.gl/i/309dht4", @"https://1234.page.link/link/dismiss"
  ];

  [urlStrings enumerateObjectsUsingBlock:^(NSString *_Nonnull urlString, NSUInteger idx,
                                           BOOL *_Nonnull stop) {
    NSURL *url = [NSURL URLWithString:urlString];
    BOOL handled = [self.service
        handleUniversalLink:url
                 completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
                   XCTAssertNil(dynamicLink, @"Non DDL returned FIRDynamicLink");
                 }];

    XCTAssertFalse(handled, @"Non DDL Universal Link was handled");
  }];
}

- (void)testHandleUniversalLinkCompletionReturnsYesForValidDDL {
  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:kURLScheme
                          userDefaults:self.userDefaults];

  NSArray<NSString *> *urlStrings = @[
    @"https://some.page.link/test", @"https://some.page.link/test-test",
    @"https://some.page.link/test_test", @"https://some.page.link/test_test-test",
    @"https://some.app.goo.gl/test_test-test", @"https://some.app.google/test_test-test",
    @"https://n8r9f.app.goo.gl/?ibi=com%2Egoogle%2EGCMTestApp%2Edev&amv=0&imv=1%2E0&link=https%3A%2F%2Fwww%2Egoogle%2Ecom",
    @"https://n8r9f.app.goo.gl/?link=https%3A%2F%2Fwww%2Egoogle%2Ecom&ibi=com%2Egoogle%2EGCMTestApp%2Edev&amv=0&imv=1%2E0",
    @"https://n8r9f.app.google/?ibi=com%2Egoogle%2EGCMTestApp%2Edev&amv=0&imv=1%2E0&link=https%3A%2F%2Fwww%2Egoogle%2Ecom",
    @"https://n8r9f.app.google/?link=https%3A%2F%2Fwww%2Egoogle%2Ecom&ibi=com%2Egoogle%2EGCMTestApp%2Edev&amv=0&imv=1%2E0"
  ];

  for (NSString *urlString in urlStrings) {
    NSURL *url = [NSURL URLWithString:urlString];

    void (^executeRequestBlock)(id, NSDictionary *, NSString *,
                                FIRNetworkRequestCompletionHandler) =
        ^(id p1, NSDictionary *requestBody, NSString *requestURLString,
          FIRNetworkRequestCompletionHandler handler) {
          NSData *data = FIRDataWithDictionary(@{}, nil);
          NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url
                                                                    statusCode:200
                                                                   HTTPVersion:nil
                                                                  headerFields:nil];
          handler(data, response, nil);
        };

    SEL executeRequestSelector = @selector(executeOnePlatformRequest:forURL:completionHandler:);
    [GULSwizzler swizzleClass:[FIRDynamicLinkNetworking class]
                     selector:executeRequestSelector
              isClassSelector:NO
                    withBlock:executeRequestBlock];

    XCTestExpectation *expectation = [self expectationWithDescription:@"handler called"];

    BOOL handled = [self.service
        handleUniversalLink:url
                 completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
                   XCTAssertNotNil(dynamicLink, @"Non DDL returned FIRDynamicLink");
                   [expectation fulfill];
                 }];

    XCTAssertTrue(handled, @"Valid DDL Universal Link was not handled");

    [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
  }
}

- (void)test_ensureInternalMethodsNotRenamed {
  // sanity check to ensure these methods has not been renamed
  // we relaying on these to be the same for tests to work properly
  // we are not exposing these methods in internal headers as we do not have "internal headers"
  // 1P apps can import all of our headers
  XCTAssertTrue([self.service
      respondsToSelector:@selector
      (handlePendingDynamicLinkRetrievalFailureWithErrorCode:errorDescription:underlyingError:)]);
  XCTAssertTrue([self.service respondsToSelector:@selector(setRetrievingPendingDynamicLink:)]);
  XCTAssertTrue([self.service respondsToSelector:@selector(dynamicLinkNetworking)]);
}

- (void)testCheckForPendingDynamicLinkReturnsImmediatelyIfAlreadyRead {
  id mockService = OCMPartialMock(self.service);
  [[mockService expect] handlePendingDynamicLinkRetrievalFailureWithErrorCode:-1
                                                             errorDescription:[OCMArg any]
                                                              underlyingError:[OCMArg any]];
  [[mockService reject] setRetrievingPendingDynamicLink:YES];

  [mockService setUpWithLaunchOptions:nil
                               apiKey:kAPIKey
                            urlScheme:nil
                         userDefaults:[NSUserDefaults standardUserDefaults]];

  [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kFIRDLReadDeepLinkAfterInstallKey];

  [mockService checkForPendingDynamicLink];

  [mockService verify];
  [mockService stopMocking];
}

- (void)testRetrievalProcessResultURLContainsAllParametersPassedToDynamicLinkInitializer {
  NSDictionary<NSString *, NSString *> *linkParameters = @{
    @"deep_link_id" : @"https://mmaksym.com/test-app1",
    @"match_message" : @"Link is uniquely matched for this device.",
    @"match_type" : @"unique",
    @"utm_campaign" : @"Maksym M Test",
    @"utm_medium" : @"test_medium",
    @"utm_source" : @"test_source",
    @"utm_content" : @"test_content",
    @"utm_term" : @"test_term",
    @"a_parameter" : @"a_value"
  };

  FIRDynamicLink *dynamicLink =
      [[FIRDynamicLink alloc] initWithParametersDictionary:linkParameters];
  FIRDLRetrievalProcessResult *result =
      [[FIRDLRetrievalProcessResult alloc] initWithDynamicLink:dynamicLink
                                                         error:nil
                                                       message:nil
                                                   matchSource:nil];

  NSURL *customSchemeURL = [result URLWithCustomURLScheme:@"scheme"];
  XCTAssertNotNil(customSchemeURL);

  // Validate URL parameters
  NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:customSchemeURL
                                              resolvingAgainstBaseURL:NO];
  XCTAssertNotNil(urlComponents);
  XCTAssertEqualObjects(urlComponents.scheme, @"scheme");

  NSMutableDictionary<NSString *, NSString *> *notEncodedParameters = [linkParameters mutableCopy];

  for (NSURLQueryItem *queryItem in urlComponents.queryItems) {
    NSString *expectedValue = notEncodedParameters[queryItem.name];
    XCTAssertNotNil(expectedValue, @"Extra parameter encoded: %@ = %@", queryItem.name,
                    queryItem.value);

    XCTAssertEqualObjects(queryItem.value, expectedValue);
    [notEncodedParameters removeObjectForKey:queryItem.name];
  }

  XCTAssertEqual(notEncodedParameters.count, 0, @"The parameters must have been encoded: %@",
                 notEncodedParameters);
}

- (void)test_multipleRequestsToRetrievePendingDeepLinkShouldNotCrash {
  id mockService = OCMPartialMock(self.service);
  [[mockService expect] handlePendingDynamicLinkRetrievalFailureWithErrorCode:-1
                                                             errorDescription:[OCMArg any]
                                                              underlyingError:[OCMArg any]];
  // swizzle method to prevent actual retrieval, this will ensure that first pending link
  // retrieval will stuck and second will fail with error
  id (^replacementBlock)(void) = (id) ^ { return nil; };

  SEL selectorToSwizzle = @selector(automaticRetrievalProcess);

  [GULSwizzler swizzleClass:[FIRDLRetrievalProcessFactory class]
                   selector:selectorToSwizzle
            isClassSelector:NO
                  withBlock:replacementBlock];

  [mockService setUpWithLaunchOptions:nil
                               apiKey:kAPIKey
                            urlScheme:kURLScheme
                         userDefaults:self.userDefaults];

  [mockService checkForPendingDynamicLink];
  // we should not crash here
  [mockService checkForPendingDynamicLink];

  [mockService verify];
  [mockService stopMocking];
  [GULSwizzler unswizzleClass:[FIRDLRetrievalProcessFactory class]
                     selector:selectorToSwizzle
              isClassSelector:NO];
}

- (void)test_retrievePendingDeepLinkShouldSetkFIRDLOpenURLKeyRegardlessOfFailures {
  [self.service setUpWithLaunchOptions:nil
                                apiKey:kAPIKey
                             urlScheme:nil
                          userDefaults:[NSUserDefaults standardUserDefaults]];
  FIRDynamicLinks<FIRDLRetrievalProcessDelegate> *delegate =
      (FIRDynamicLinks<FIRDLRetrievalProcessDelegate> *)self.service;

  // Error Result to pass
  FIRDLRetrievalProcessResult *result = [[FIRDLRetrievalProcessResult alloc]
      initWithDynamicLink:nil
                    error:[NSError errorWithDomain:@"unknown domain" code:500 userInfo:nil]
                  message:nil
              matchSource:nil];

  FIRDLDefaultRetrievalProcessV2 *defaultRetrievalProcess = [FIRDLDefaultRetrievalProcessV2 alloc];

  [delegate retrievalProcess:defaultRetrievalProcess completedWithResult:result];

  NSString *kFIRDLOpenURLKey = @"com.google.appinvite.openURL";
  XCTAssertEqual([[NSUserDefaults standardUserDefaults] boolForKey:kFIRDLOpenURLKey], YES,
                 @"kFIRDLOpenURL key should be set regardless of failures");
}

- (void)test_passRetrievedDynamicLinkToApplicationDelegatesProperly {
  // Creating ApplicationDelegate partial mock object.
  id applicationDelegate = OCMPartialMock([UIApplication sharedApplication].delegate);
  // Creating FIRDynamicLinks partial mock object.
  id firebaseDynamicLinks = OCMPartialMock(self.service);
  // Stubbing Application delegate to return YES when application:openURL:options method is called.
  // Not sure why this is required as we are not concerned about its return, but without this, the
  // test will throw NSInvalidArgumentException with message "unrecognized selector sent to
  // instance".
  OCMStub([applicationDelegate application:[OCMArg any] openURL:[OCMArg any] options:[OCMArg any]])
      .andReturn(YES);
  // Stubbing firebase dynamiclinks instance to return YES when isOpenUrlMethodPresentInAppDelegate
  // is called.
  OCMStub([firebaseDynamicLinks isOpenUrlMethodPresentInAppDelegate:[OCMArg any]]).andReturn(YES);

  // Executing the function with a URL.
  NSURL *url = [NSURL URLWithString:@"http://www.google.com"];
  [firebaseDynamicLinks passRetrievedDynamicLinkToApplication:url];

  // Verifying the application:openURL:options method is called in AppDelegate.
  OCMVerify([applicationDelegate application:[OCMArg any] openURL:url options:[OCMArg any]]);
}

#pragma mark - Self-diagnose tests

- (void)testSelfDiagnoseWithNilCompletion {
  [FIRDynamicLinks performDiagnosticsWithCompletion:nil];
}

- (void)testSelfDiagnoseCompletionCalled {
  XCTestExpectation *expectation =
      [self expectationWithDescription:@"Self diagnose completion block must be called"];

  [FIRDynamicLinks
      performDiagnosticsWithCompletion:^(NSString *_Nonnull diagnosticOutput, BOOL hasErrors) {
        XCTAssert(diagnosticOutput.length > 0, @"Diagnostic expected to provide output");
        [expectation fulfill];
      }];

  [self waitForExpectationsWithTimeout:2.0 handler:nil];
}

#pragma mark - Custom domain tests
- (void)testValidCustomDomainNames {
  // Entries in plist file:
  //  https://google.com
  //  https://google.com/one
  //  https://a.firebase.com/mypath

  NSArray<NSString *> *urlStrings = @[
    @"https://google.com/mylink",             // Short FDL starting with 'https://google.com'
    @"https://google.com/one",                // Short FDL starting with 'https://google.com'
    @"https://google.com/one/",               // Short FDL starting with 'https://google.com'
    @"https://google.com/one?",               // Short FDL starting with 'https://google.com'
    @"https://google.com/one/mylink",         // Short FDL starting with  'https://google.com/one'
    @"https://a.firebase.com/mypath/mylink",  // Short FDL starting https://a.firebase.com/mypath
    @"https://google.com/somepath?link=https://somedomain",
    @"https://google.com/somepath/?link=https://somedomain",
    @"https://google.com/somepath/somepath2?link=https://somedomain",
    @"https://google.com/somepath/somepath2/?link=https://somedomain",
    @"https://google.com/somepath?utm_campgilink=someval"
  ];

  NSArray<NSString *> *longFDLURLStrings = @[
    @"https://a.firebase.com/mypath/?link=https://abcd&test=1",  // Long FDL starting with
                                                                 // https://a.firebase.com/mypath
    @"https://google.com?link=http://abcd",   // Long FDL starting with  'https://google.com'
    @"https://google.com/?link=http://abcd",  // Long FDL starting with  'https://google.com'
    @"https://google.com?link=https://somedomain&some=qry",   // Long FDL with link param as another
                                                              // argument.
    @"https://google.com/?link=https://somedomain&some=qry",  // Long FDL with link param as another
                                                              // argument.
    @"https://google.com?some=qry&link=https://somedomain",   // Long FDL with link param as second
                                                              // argument.
    @"https://google.com/?some=qry&link=https://somedomain",  // Long FDL with link param as second
                                                              // argument
    @"https://google.com/?a=b&c=d&link=https://somedomain&y=z",    // Long FDL with link param as
                                                                   // middle argument argument
    @"https://google.com?some=qry&link=https%3A%2F%2Fsomedomain",  // Long FDL with Url encoded link
                                                                   // param
  ];
  for (NSString *urlString in urlStrings) {
    NSURL *url = [NSURL URLWithString:urlString];
    BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];

    XCTAssertTrue(matchesShortLinkFormat, @"URL did not validate as short link: %@", url);
  }
  for (NSString *urlString in longFDLURLStrings) {
    NSURL *url = [NSURL URLWithString:urlString];
    BOOL matchesLongLinkFormat = [self.service canParseUniversalLinkURL:url];

    XCTAssertTrue(matchesLongLinkFormat, @"URL did not validate as long link: %@", url);
  }
}

- (void)testInvalidCustomDomainNames {
  // Entries in plist file:
  //  https://google.com
  //  https://google.com/one
  //  https://a.firebase.com/mypath

  NSArray<NSString *> *urlStrings = @[
    @"google.com",                             // Valid domain. No scheme.
    @"https://google.com",                     // Valid domain. No path after domainURIPrefix.
    @"https://google.com/",                    // Valid domain. No path after domainURIPrefix.
    @"https://google.co.in/mylink",            // No matching domainURIPrefix.
    @"https://google.com/?some=qry",           // Valid domain with no path and link param
    @"https://google.com/?some=qry&link=bla",  // Valid domain with no path and no valid link param
    @"https://firebase.com/mypath",            // No matching domainURIPrefix: Invalid (sub)domain.
    @"https://b.firebase.com/mypath",          // No matching domainURIPrefix: Invalid subdomain.
    @"https://a.firebase.com/mypathabc",       // No matching domainURIPrefix: Invalid subdomain.
    @"mydomain.com",                           // https scheme not specified for domainURIPrefix.
    @"http://mydomain",  // Domain not in plist. No path after domainURIPrefix.
    @"https://somecustom.com?", @"https://somecustom.com/?",
    @"https://somecustom.com?somekey=someval",
    @"https://google.com?some=qry&somelink=https%3A%2F%2Fsomedomain",  // Having somelink param
                                                                       // instead of link param to
                                                                       // confuse validation.
    @"https://a.firebase.com/mypaths?some=qry&link=https%3A%2F%2Fsomedomain",  // Additional 's' in
                                                                               // path param
    @"https://a.firebase.com/mypath/?some=qry#other=b&link=https://somedomain",  // link param comes
                                                                                 // in fragmentation
    @"https://a.firebase.com/mypath/?some=qry#other=b&link=https%3A%2F%2Fsomedomain",  // link param
                                                                                       // which is
                                                                                       // url
                                                                                       // encoded
                                                                                       // and comes
                                                                                       // in
                                                                                       // fragmentation.
    @"https://google.com?link=https1://abcd",  // link query param is not a valid http link
  ];

  for (NSString *urlString in urlStrings) {
    NSURL *url = [NSURL URLWithString:urlString];
    BOOL matchesShortLinkFormat = [self.service canParseUniversalLinkURL:url];

    XCTAssertFalse(matchesShortLinkFormat,
                   @"Non-DDL domain URL matched short link format with URL: %@", url);
  }
}

#pragma mark - Private Helpers

- (void)removeAllFIRApps {
  NSDictionary *apps = [FIRApp allApps];
  for (FIRApp *app in apps.allValues) {
    [app deleteApp:^(BOOL success) {
      if (!success) {
        NSLog(@"Error deleting FIRApp before tests - config tests may fail.");
      }
    }];
  }
}

#pragma clang pop

@end
