//
//  WebClientTests.swift
//  Astro
//
//  Created by Justin Vaillancourt on 2015-05-27.
//  Copyright (c) 2015 Mobify Research & Development Inc. All rights reserved.
//

import XCTest
import WebKit
@testable import Astro

private enum Request {
    case GET(URL)
    case POST(URL)

    var request: URLRequest {
        switch self {
        case .GET(let url):
            return URLRequest(url: url)
        case .POST(let url):
            var request = URLRequest(url: url)
            request.httpMethod = "POST"
            return request
        }
    }

    var URL: URL {
        switch self {
        case .GET(let url):
            return url
        case .POST(let url):
            return url
        }
    }
}

enum NavigationEvent {
    case success(URL)
    case failure(URL)

    var description: String {
        switch self {
        case .success(let url):
            return "HandleNavigationEvent(url: \(url))"
        case .failure(let url):
            return "PageFailedEvent(url: \(url))"
        }
    }

    var isSuccess: Bool {
        switch self {
        case .success: return true
        default: return false
        }
    }
}

extension NavigationEvent: Equatable { }

func ==(lhs: NavigationEvent, rhs: NavigationEvent) -> Bool {
    switch (lhs, rhs) {
    case (.success(let a), .success(let b)) where a == b: return true
    case (.failure(let a), .failure(let b)) where a == b: return true
    default: return false
    }
}

private extension AstroTestCase {
    func assertEqual<T: Equatable>(_ a: T?, _ b: T?, _ testName: String, _ webViewAdaptorType: WebClient.Type, _ message: String) {
        XCTAssertEqual(a, b, "\(testName) -- \(webViewAdaptorType): NOT EQUAL - \(message)\n    \(a.debugDescription)\n    \(b.debugDescription)")
    }

    func assertEqualArrays<T: Equatable>(_ a: [T], _ b: [T], _ testName: String, _ webViewAdaptorType: WebClient.Type, _ message: String) {
        XCTAssertEqual(a, b, "\(testName) -- \(webViewAdaptorType): \(message)\n    \(a)\n    \(b)")
    }
}

class WebClientTests: AstroTestCase {
    private struct WebClientTest {
        struct Expected { // swiftlint:disable:this nesting
            let numberOfPageLoads: Int
            let pageHandledEvents: [NavigationEvent]
            let pageFailedEvents: [NavigationEvent]
            let endingURL: URL?

            init(numberOfPageLoads: Int = 1, pageHandledEvents: [NavigationEvent] = [], pageFailedEvents: [NavigationEvent] = [], endingURL: URL? = nil) {
                self.numberOfPageLoads = numberOfPageLoads
                self.pageHandledEvents = pageHandledEvents
                self.pageFailedEvents = pageFailedEvents
                self.endingURL = endingURL
            }
        }

        let name: String
        let request: Request
        let expected: Expected
    }

    private let basicHandleNavigateEvent = NavigationEvent.success(testUrl("basic.html"))
    private let postHandleNavigationEvent = NavigationEvent.success(testUrl("http/post"))
    private let postWithRedirectHandleNavigationEvent = NavigationEvent.success( testUrl("http/post-with-redirect"))
    private let getFormHandleNavigationEvent = NavigationEvent.success(testUrl("http/get-form?key=value"))
    private let getFromWithRedirectHandleNavigationEvent = NavigationEvent.success(testUrl("http/get-form-with-redirect?key=value"))

    override func setUp() {
        super.setUp()
        XCTAssertTrue(ensureFixtureServer(), "Fixture server must be running")
    }

    /// Creates an AstroWebViewAdaptor based on the inputs with a delegate set and configured
    /// with the given closures.
    /// Returns the adaptor and the delegate in a tuple
    private func client(isWKWebView: Bool, didFinishLoadingClosure: (() -> Void)? = nil, didHandleNavigationClosure: (() -> Void)? = nil, didFailToLoadClosure: (() -> Void)? = nil) -> (AstroWebViewAdaptor, TestWebClientDelegate) {
        let client = isWKWebView ? WKWebViewAdaptor(webView: WKWebView()) : UIWebViewAdaptor(webView: UIWebView())
        let delegate = TestWebClientDelegate()
        delegate.didFinishLoadingClosure = didFinishLoadingClosure
        delegate.didFailToLoadClosure = didFailToLoadClosure
        delegate.handleNavigationClosure = didHandleNavigationClosure
        client.webClientDelegate = delegate
        return (client, delegate)
    }

    /// Sets up the given test with the parameters given, then creates a dispatch group and
    /// a test expectation based on the name of the test.
    /// Returns the created test, the dispatch group and the test expectation
    private func setupTest(named name: String, request: Request, expected: WebClientTest.Expected) -> (WebClientTest, DispatchGroup, XCTestExpectation) {
        let test = WebClientTest(name: name, request: request, expected: expected)
        let expectation = self.expectation(description: name)

        //Enter twice, once for the UIWebView test and once for the WKWebView test
        let group = DispatchGroup()
        group.enter()
        group.enter()
        return (test, group, expectation)
    }

    func testBasicNavigation() {
        //Test setup
        let request = Request.GET(testUrl("basic.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "Basic HTML page should load", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testPageWithFrames() {
        let request = Request.GET(testUrl("frames.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "Basic HTML page with frames", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testPageWithImages() {
        let request = Request.GET(testUrl("images.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "page with images", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testPageWithScripts() {
        let request = Request.GET(testUrl("scripts.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "page with scripts", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testPageWithAJAX() {
        let request = Request.GET(testUrl("ajax.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "page with ajax", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testPageWithCapturing() {
        let request = Request.GET(testUrl("capturing.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "page with capturing", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func test404Page() {
        let request = Request.GET(testUrl("http/404.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "404 page", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func test500Page() {
        let request = Request.GET(testUrl("http/500.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "500 page", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testLinkClicked() {
        let request = Request.GET(testUrl("click-navigation-after-load.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, pageHandledEvents: [basicHandleNavigateEvent], endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "link clicked", request: request, expected: expected)

        //Since we are wanting to test that the handle navigation is called, we enter twice more
        group.enter()
        group.enter()

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave, didHandleNavigationClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave, didHandleNavigationClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testLinkClickedBeforeLoad() {
        let request = Request.GET(testUrl("click-navigation-before-load.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, pageHandledEvents: [basicHandleNavigateEvent], endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "link clicked before load", request: request, expected: expected)
        //Because we redirect before loading, we only load once, so the `leaving` should be on finish loading
        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave  )
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testJSNavigation() {
        let request = Request.GET(testUrl("javascript-navigation.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, pageHandledEvents: [basicHandleNavigateEvent], endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "javascript navigation", request: request, expected: expected)

        //Since we are wanting to test that the handle navigation is called, we enter twice more
        group.enter()
        group.enter()

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave, didHandleNavigationClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave, didHandleNavigationClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testFormPostWithNav() {
        let request = Request.GET(testUrl("post-navigation.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 2, endingURL: testUrl("http/post"))
        let (test, group, expectation) = setupTest(named: "form post with nav", request: request, expected: expected)

        var numberOfPageLoads = 0
        let didFinishLoading = {
            numberOfPageLoads += 1
            if numberOfPageLoads == expected.numberOfPageLoads {
                group.leave()
                numberOfPageLoads = 0
            }
        }

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: didFinishLoading)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: didFinishLoading)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testFormPostWithRedirectNav() {
        let request = Request.GET(testUrl("post-with-redirect-navigation.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 2, endingURL: testUrl("basic.html"))
        let (test, group, expectation) = setupTest(named: "form post with nav", request: request, expected: expected)

        var numberOfPageLoads = 0
        let didFinishLoading = {
            numberOfPageLoads += 1
            if numberOfPageLoads == expected.numberOfPageLoads {
                group.leave()
                numberOfPageLoads = 0
            }
        }

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: didFinishLoading)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: didFinishLoading)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testFormGet() {
        let request = Request.GET(testUrl("get-form-navigation.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, pageHandledEvents: [getFormHandleNavigationEvent], endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "form get", request: request, expected: expected)
        //Since we are wanting to test that the handle navigation is called, we enter twice more
        group.enter()
        group.enter()

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave, didHandleNavigationClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave, didHandleNavigationClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testFormGetWithTarget() {
        let request = Request.GET(testUrl("get-form-with-target.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "form get", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)
        client.allowExplicitNavigation(["http\\/get-form"])
        uiClient.allowExplicitNavigation(["http\\/get-form"])

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testFormGetWithRedirect() {
        let request = Request.GET(testUrl("get-form-with-redirect-navigation.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, pageHandledEvents: [getFromWithRedirectHandleNavigationEvent], endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "form get with redirect", request: request, expected: expected)

        //Since we are wanting to test that the handle navigation is called, we enter twice more
        group.enter()
        group.enter()

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave, didHandleNavigationClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave, didHandleNavigationClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testGetWithParams() {
        let request = Request.GET(testUrl("basic.html?a=b&c=d"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "get with params", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testFormPost() {
        let request = Request.POST(testUrl("http/post"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "form post", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testFormPostWithRedirect() {
        let request = Request.POST(testUrl("http/post-with-redirect"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: testUrl("basic.html"))
        let (test, group, expectation) = setupTest(named: "form post with redirect", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testRedirect() {
        let request = Request.GET(testUrl("http/redirect"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: testUrl("basic.html"))
        let (test, group, expectation) = setupTest(named: "redirect", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    // Adding suffix Z and A to separate ReloadWKWebView and ReloadUIWebView test
    // Reload test was failing on BuddyBuild non-deterministically due to timing of when
    // UIWebView and WKWebview reload happens. Current workaround is to have the tests
    // run as far apart as possible so they don't interfere with another.
    func testZ_ReloadWKWebView() {
        let request = Request.GET(testUrl("reload.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 2, endingURL: request.URL)
        let test = WebClientTest(name: name, request: request, expected: expected)
        let expectation = self.expectation(description: name)

        var numberOfPageLoads = 0
        let didFinishLoading = {
            numberOfPageLoads += 1
            if numberOfPageLoads == expected.numberOfPageLoads {
                expectation.fulfill()
            }
        }

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: didFinishLoading)
        client.load(test.request.request)

        waitForExpectations(timeout: 20.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
        }
    }

    func testA_ReloadUIWebView() {
        let request = Request.GET(testUrl("reload.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 2, endingURL: request.URL)
        let test = WebClientTest(name: name, request: request, expected: expected)
        let expectation = self.expectation(description: name)

        var numberOfPageLoads = 0
        let didFinishLoading = {
            numberOfPageLoads += 1
            if numberOfPageLoads == expected.numberOfPageLoads {
                expectation.fulfill()
            }
        }

        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: didFinishLoading)
        uiClient.load(test.request.request)

        waitForExpectations(timeout: 20.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testJavascriptIframe() {
        let request = Request.GET(testUrl("javascript-iframe-after-load.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "javascript iframe", request: request, expected: expected)
        
        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testHashLinkLoad() {
        let request = Request.GET(testUrl("hash-link-change.html#test"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: testUrl("hash-link-change.html#hello"))
        let (test, group, expectation) = setupTest(named: "hash link load", request: request, expected: expected)
        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testHashLinkChange() {
        let request = Request.GET(testUrl("hash-link-change.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, endingURL: testUrl("hash-link-change.html#hello"))
        let (test, group, expectation) = setupTest(named: "hash link change", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testNonExistentHost() {
        let request = Request.GET(URL(string: "http://nonexistent/nonexistent-host.html")!)
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, pageHandledEvents: [], pageFailedEvents: [.failure(URL(string: "http://nonexistent/nonexistent-host.html")!)], endingURL: nil)
        let (test, group, expectation) = setupTest(named: "nonexsitent host", request: request, expected: expected)

        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: nil, didHandleNavigationClosure: nil, didFailToLoadClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: nil, didHandleNavigationClosure: nil, didFailToLoadClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    func testNonExistentServer() {
        let request = Request.GET(URL(string: "http://localhost:89/nonexistent-server.html")!)
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, pageHandledEvents: [], pageFailedEvents: [.failure(URL(string: "http://localhost:89/nonexistent-server.html")!)], endingURL: nil)
        let (test, group, expectation) = setupTest(named: "nonexistent server", request: request, expected: expected)

        //Create client and load request
        let (client, delegate) = self.client(isWKWebView: true, didFinishLoadingClosure: nil, didHandleNavigationClosure: nil, didFailToLoadClosure: group.leave)
        client.load(test.request.request)
        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: nil, didHandleNavigationClosure: nil, didFailToLoadClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: delegate, webClient: client)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    // This test has known inconsistent behaviour against WKWebView. When running javascript redirects we sometimes see the initial page
    // finish loading before the redirect comes through (didFinishNavigation is called), and sometimes vice-versa. This makes it impossible
    // to write a test that will pass consitently against WKWebview for this case. We believe our code to be correct and this to be a subtlety
    // of WKWebview. We will continue testing this only on UIWebView for now: https://mobify.atlassian.net/browse/HYB-497
    func testJavaScriptRedirect() {
        let request = Request.GET(testUrl("javascript-immediate-navigation.html"))
        let expected = WebClientTest.Expected(numberOfPageLoads: 1, pageHandledEvents: [basicHandleNavigateEvent], endingURL: request.URL)
        let (test, group, expectation) = setupTest(named: "javascript redirect", request: request, expected: expected)

        let (uiClient, uiDelegate) = self.client(isWKWebView: false, didFinishLoadingClosure: group.leave, didHandleNavigationClosure: group.leave)
        uiClient.load(test.request.request)

        group.notify(queue: DispatchQueue.main) {
            expectation.fulfill()
        }

        waitForExpectations(timeout: 10.0) { error in
            XCTAssertNil(error)
            self.verifyWebClientTest(test, delegate: uiDelegate, webClient: uiClient)
        }
    }

    private func verifyWebClientTest(_ test: WebClientTest, delegate: TestWebClientDelegate, webClient: WebClient) {
        let webClientType = type(of: webClient.self)

        let numStartCalls = delegate.numberOfPageDidStartLoadingCalls
        let numFinishCalls = delegate.numberOfPageLoads

        let failedEvents = delegate.navigationEvents.filter { !$0.isSuccess }
        let successEvents = delegate.navigationEvents.filter { $0.isSuccess }

        let endingUrl = webClient.currentURL?.absoluteString.count == 0 ? nil : webClient.currentURL
        let expectedEndingUrl = test.expected.endingURL

        assertEqual(numFinishCalls, test.expected.numberOfPageLoads, test.name, webClientType, "number of page loads")
        assertEqualArrays(successEvents, test.expected.pageHandledEvents, test.name, webClientType, "handle navigation events")
        assertEqual(numStartCalls, numFinishCalls, test.name, webClientType, "number of page start and finish calls not equal")
        assertEqual(endingUrl, expectedEndingUrl, test.name, webClientType, "ending url")
        assertEqualArrays(failedEvents, test.expected.pageFailedEvents, test.name, webClientType, "handle page failed events")
    }
}
