//
//  ReCaptchaWebViewManager__Tests.swift
//  ReCaptcha
//
//  Created by Flávio Caetano on 13/04/17.
//  Copyright © 2018 ReCaptcha. All rights reserved.
//

@testable import ReCaptcha

import WebKit
import XCTest


class ReCaptchaWebViewManager__Tests: XCTestCase {

    fileprivate var apiKey: String!
    fileprivate var presenterView: UIView!

    override func setUp() {
        super.setUp()

        presenterView = UIApplication.shared.keyWindow!
        apiKey = String(arc4random())
    }

    override func tearDown() {
        presenterView = nil
        apiKey = nil

        super.tearDown()
    }

    // MARK: Validate

    func test__Validate__Token() {
        let exp1 = expectation(description: "load token")
        var result1: ReCaptchaResult?

        // Validate
        let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey)
        manager.configureWebView { _ in
            XCTFail("should not ask to configure the webview")
        }

        manager.validate(on: presenterView) { response in
            result1 = response
            exp1.fulfill()
        }

        waitForExpectations(timeout: 10)


        // Verify
        XCTAssertNotNil(result1)
        XCTAssertNil(result1?.error)
        XCTAssertEqual(result1?.token, apiKey)


        // Validate again
        let exp2 = expectation(description: "reload token")
        var result2: ReCaptchaResult?

        // Validate
        manager.validate(on: presenterView) { response in
            result2 = response
            exp2.fulfill()
        }

        waitForExpectations(timeout: 10)


        // Verify
        XCTAssertNotNil(result2)
        XCTAssertNil(result2?.error)
        XCTAssertEqual(result2?.token, apiKey)
    }


    func test__Validate__Show_ReCaptcha() {
        let exp = expectation(description: "show recaptcha")

        // Validate
        let manager = ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}")
        manager.configureWebView { _ in
            exp.fulfill()
        }

        manager.validate(on: presenterView) { _ in
            XCTFail("should not call completion")
        }

        waitForExpectations(timeout: 10)
    }


    func test__Validate__Message_Error() {
        var result: ReCaptchaResult?
        let exp = expectation(description: "message error")

        // Validate
        let manager = ReCaptchaWebViewManager(messageBody: "\"foobar\"")
        manager.configureWebView { _ in
            XCTFail("should not ask to configure the webview")
        }

        manager.validate(on: presenterView, resetOnError: false) { response in
            result = response
            exp.fulfill()
        }

        waitForExpectations(timeout: 10)

        // Verify
        XCTAssertNotNil(result)
        XCTAssertEqual(result?.error, .wrongMessageFormat)
        XCTAssertNil(result?.token)
    }

    func test__Validate__JS_Error() {
        var result: ReCaptchaResult?
        let exp = expectation(description: "js error")

        // Validate
        let manager = ReCaptchaWebViewManager(messageBody: "foobar")
        manager.configureWebView { _ in
            XCTFail("should not ask to configure the webview")
        }

        manager.validate(on: presenterView, resetOnError: false) { response in
            result = response
            exp.fulfill()
        }

        waitForExpectations(timeout: 10)

        // Verify
        XCTAssertNotNil(result)
        XCTAssertNotNil(result?.error)
        XCTAssertNil(result?.token)

        switch result!.error! {
        case .unexpected(let error as NSError):
            XCTAssertEqual(error.code, WKError.javaScriptExceptionOccurred.rawValue)
        default:
            XCTFail("Unexpected error received")
        }
    }

    // MARK: Configure WebView

    func test__Configure_Web_View__Empty() {
        let exp = expectation(description: "configure webview")

        // Configure WebView
        let manager = ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}")
        manager.validate(on: presenterView) { _ in
            XCTFail("should not call completion")
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            exp.fulfill()
        }

        waitForExpectations(timeout: 10)
    }

    func test__Configure_Web_View() {
        let exp = expectation(description: "configure webview")

        // Configure WebView
        let manager = ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}")
        manager.configureWebView { [unowned self] webView in
            XCTAssertEqual(webView.superview, self.presenterView)
            exp.fulfill()
        }

        manager.validate(on: presenterView) { _ in
            XCTFail("should not call completion")
        }

        waitForExpectations(timeout: 10)
    }

    func test__Configure_Web_View__Called_Once() {
        var count = 0
        let exp0 = expectation(description: "configure webview")

        // Configure WebView
        let manager = ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}")
        manager.configureWebView { _ in
            if count < 3 {
                manager.webView.evaluateJavaScript("execute();") { XCTAssertNil($1) }
            }

            count += 1
            exp0.fulfill()
        }

        manager.validate(on: presenterView) { _ in
            XCTFail("should not call completion")
        }

        waitForExpectations(timeout: 10)

        let exp1 = expectation(description: "waiting for extra calls")
        DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: exp1.fulfill)
        waitForExpectations(timeout: 2)

        XCTAssertEqual(count, 1)
    }

    func test__Configure_Web_View__Called_Again_With_Reset() {
        let exp0 = expectation(description: "configure webview 0")

        let manager = ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}")
        manager.validate(on: presenterView) { _ in
            XCTFail("should not call completion")
        }

        // Configure Webview
        manager.configureWebView { _ in
            manager.webView.evaluateJavaScript("execute();") { XCTAssertNil($1) }
            exp0.fulfill()
        }

        waitForExpectations(timeout: 10)

        // Reset and ensure it calls again
        let exp1 = expectation(description: "configure webview 1")

        manager.configureWebView { _ in
            manager.webView.evaluateJavaScript("execute();") { XCTAssertNil($1) }
            exp1.fulfill()
        }

        manager.reset()
        waitForExpectations(timeout: 10)
    }

    // MARK: Stop

    func test__Stop() {
        let exp = expectation(description: "stop loading")

        // Stop
        let manager = ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}")
        manager.stop()
        manager.configureWebView { _ in
            XCTFail("should not ask to configure the webview")
        }

        manager.validate(on: presenterView) { _ in
            XCTFail("should not validate")
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            exp.fulfill()
        }

        waitForExpectations(timeout: 10)
    }

    // MARK: Setup

    func test__Key_Setup() {
        let exp = expectation(description: "setup key")
        var result: ReCaptchaResult?

        // Validate
        let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey)
        manager.configureWebView { _ in
            XCTFail("should not ask to configure the webview")
        }

        manager.validate(on: presenterView) { response in
            result = response
            exp.fulfill()
        }

        waitForExpectations(timeout: 10)

        XCTAssertNotNil(result)
        XCTAssertNil(result?.error)
        XCTAssertEqual(result?.token, apiKey)
    }

    func test__Endpoint_Setup() {
        let exp = expectation(description: "setup endpoint")
        let endpoint = ReCaptcha.Endpoint.alternate.getURL(locale: nil)
        var result: ReCaptchaResult?

        let manager = ReCaptchaWebViewManager(messageBody: "{token: endpoint}", endpoint: endpoint)
        manager.configureWebView { _ in
            XCTFail("should not ask to configure the webview")
        }

        manager.validate(on: presenterView) { response in
            result = response
            exp.fulfill()
        }

        waitForExpectations(timeout: 10)

        XCTAssertNotNil(result)
        XCTAssertNil(result?.error)
        XCTAssertEqual(result?.token, endpoint)
    }

    // MARK: Reset

    func test__Reset() {
        let exp1 = expectation(description: "fail on first execution")
        var result1: ReCaptchaResult?

        // Validate
        let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey, shouldFail: true)
        manager.configureWebView { _ in
            XCTFail("should not ask to configure the webview")
        }

        // Error
        manager.validate(on: presenterView, resetOnError: false) { result in
            result1 = result
            exp1.fulfill()
        }

        waitForExpectations(timeout: 10)
        XCTAssertEqual(result1?.error, .wrongMessageFormat)

        // Resets and tries again
        let exp2 = expectation(description: "validates after reset")
        var result2: ReCaptchaResult?

        manager.reset()
        manager.validate(on: presenterView, resetOnError: false) { result in
            result2 = result
            exp2.fulfill()
        }

        waitForExpectations(timeout: 10)

        XCTAssertNil(result2?.error)
        XCTAssertEqual(result2?.token, apiKey)
    }

    func test__Validate__Reset_On_Error() {
        let exp = expectation(description: "fail on first execution")
        var result: ReCaptchaResult?

        // Validate
        let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey, shouldFail: true)
        manager.configureWebView { _ in
            XCTFail("should not ask to configure the webview")
        }

        // Error
        manager.validate(on: presenterView, resetOnError: true) { response in
            result = response
            exp.fulfill()
        }

        waitForExpectations(timeout: 10)

        XCTAssertNil(result?.error)
        XCTAssertEqual(result?.token, apiKey)
    }

    // MARK: Force Challenge Visible

    func test__Force_Visible_Challenge() {
        let manager = ReCaptchaWebViewManager()

        // Initial value
        XCTAssertFalse(manager.forceVisibleChallenge)

        // Set True
        manager.forceVisibleChallenge = true
        XCTAssertEqual(manager.webView.customUserAgent, "Googlebot/2.1")

        // Set False
        manager.forceVisibleChallenge = false
        XCTAssertNotEqual(manager.webView.customUserAgent?.isEmpty, false)
    }
}
