//
//  ApplePayPluginTests.swift
//  Astro
//
//  Created by Karl Schmidt on 2015-11-23.
//  Copyright © 2015 Mobify Research & Development Inc. All rights reserved.
//

import XCTest
import PassKit
@testable import Astro

// These tests are disabled for now - they seem to conflict with the AstroFileUtilsTests tests 
// (crashes from AstroFileUtilsTests occur, but in a non-main thread where a Apple Pay stack is found
class ApplePayPluginTests: AstroTestCase {
    var messageBus: MessageBus!
    var pluginResolver: StubPluginResolver!
    var capturedResult: CapturedRpcMethodResult!

    var applePayPlugin: ApplePayPlugin!

    override func setUp() {
        messageBus = MessageBus()
        pluginResolver = StubPluginResolver()
        capturedResult = CapturedRpcMethodResult()

        applePayPlugin = ApplePayPlugin(address: "ApplePayPlugin:0", messageBus: messageBus, pluginResolver: pluginResolver, options: nil)
    }

    func testCompletionHandlerMap() {
        typealias TestCompletionHandlerType = (String) -> Int
        let completionHandlerMap = CompletionHandlerMap<TestCompletionHandlerType>()

        let returnTypeTestValue = 5
        AssertNil(completionHandlerMap.getCompletionHandler(0))
        completionHandlerMap.addCompletionHandler({ testString in
            XCTAssertTrue(testString == "test")
            return returnTypeTestValue
        })

        guard let testCompletion = completionHandlerMap.getCompletionHandler(0) else {
            XCTFail()
            return
        }

        XCTAssertTrue(testCompletion("test") == returnTypeTestValue)

        let returnTypeTestValue2 = 6
        completionHandlerMap.addCompletionHandler({ testString in
            XCTAssertTrue(testString == "test2")
            return returnTypeTestValue2
        })

        guard let testCompletion2 = completionHandlerMap.getCompletionHandler(1) else {
            XCTFail()
            return
        }
        XCTAssertTrue(testCompletion2("test2") == returnTypeTestValue2)

        guard let testCompletion1Repeat = completionHandlerMap.getCompletionHandler(0) else {
            XCTFail()
            return
        }
        XCTAssertTrue(testCompletion1Repeat("test") == returnTypeTestValue)

        completionHandlerMap.removeCompletionHandler(0)
        XCTAssertTrue(completionHandlerMap.getCompletionHandler(0) == nil)
    }

    // These tests seem superfluous, but are good to have around in case iOS SDK behaviour changes
    // from what we are expecting
    func testCanBeSetUp() {
        if #available(iOS 8.3, *) {
            XCTAssertTrue(ApplePayPlugin.canBeSetUp())
        } else {
            XCTAssertFalse(ApplePayPlugin.canBeSetUp())
        }
    }

    func testAvailability() {
        applePayPlugin.getAvailability(["AmEx"], respond: capturedResult.callback)
        if #available(iOS 9.0, *) {
            XCTAssertTrue(capturedResult.result as? String == "ready")
        } else {
            XCTAssertTrue(capturedResult.result as? String == "unavailable")
        }
    }

    /*func testStartPayment() {
        let paymentRequestData: JSONObject = [
            "merchantIdentifier": "merchant.com.mobify.astro.test",
            "paymentSummaryItems": [["label": "test", "amount": 1]],
            "countryCode": "US",
            "currencyCode": "USD",
            "supportedNetworks": ["AmEx", "Visa", "MasterCard"],
            "merchantCapabilities": ["3DS", "EMV"],
            "requiredBillingAddressFields": ["PostalAddress", "Phone"],
            "requiredShippingAddressFields": ["PostalAddress", "Phone"],
        ]

        applePayPlugin.startPayment(paymentRequestData, respond: capturedResult.callback)
        XCTAssertTrue(capturedResult.result == nil)
    }

    func testPaymentAuthorized() {
        let receiver = TestReceiver(address: WORKER_ADDRESS)
        messageBus.listen(applePayPlugin.eventAddress, receiver: receiver)

        expectAssertion( {expectation in
            let shippingMethod = PKShippingMethod(label: "Test", amount: 1)
            shippingMethod.identifier = "TestId"

            let testPayment = MockPKPayment()

            self.applePayPlugin.paymentAuthorizationViewController(PKPaymentAuthorizationViewController(), didAuthorizePayment: testPayment, completion: { (authorizationStatus: PKPaymentAuthorizationStatus) -> Void in
                XCTAssertTrue(authorizationStatus == .Success)
                expectation.fulfill()
            })

            let receivedMessage = receiver.receivedMessage as? EventMessage
            XCTAssertTrue(receivedMessage != nil)
            XCTAssertTrue(receivedMessage!.eventName == "didAuthorizePayment")
            self.applePayPlugin.didAuthorizePaymentCompletion(receivedMessage!.params["completionId"] as! NSNumber, authorizationStatus: .Success, respond: self.capturedResult.callback)
        })
    }

    func testSelectShippingAddress() {
        let receiver = TestReceiver(address: WORKER_ADDRESS)
        messageBus.listen(applePayPlugin.eventAddress, receiver: receiver)

        let address = ApplePayPluginTests.createABRecord("", city: "Vancouver", state: "BC", postalCode: "V6B", countryCode: "CA")

        expectAssertion( { expectation in
            self.applePayPlugin.paymentAuthorizationViewController(PKPaymentAuthorizationViewController(), didSelectShippingAddress: address, completion: { (StatusBarStateViewController, shippingMethods: [PKShippingMethod], summaryItems: [PKPaymentSummaryItem]) -> Void in
                XCTAssertTrue(shippingMethods.count == 0)
                XCTAssertTrue(summaryItems.count == 1)
                let paymentSummaryItem = summaryItems[0]
                XCTAssertTrue(paymentSummaryItem.label == "test")
                XCTAssertTrue(paymentSummaryItem.amount == 1)
                expectation.fulfill()
            })

            let receivedMessage = receiver.receivedMessage as? EventMessage
            XCTAssertTrue(receivedMessage != nil)
            XCTAssertTrue(receivedMessage!.eventName == "didSelectShippingAddress")
            self.applePayPlugin.didSelectShippingAddressCompletion(receivedMessage!.params["completionId"] as! NSNumber, authorizationStatus: .Success, shippingMethods: [], paymentSummaryItems: [PKPaymentSummaryItem(label: "test", amount: 1)], respond: self.capturedResult.callback)
        })
    }

    func testSelectShippingMethod() {
        let receiver = TestReceiver(address: WORKER_ADDRESS)
        messageBus.listen(applePayPlugin.eventAddress, receiver: receiver)

        expectAssertion( { expectation in
            let shippingMethod = PKShippingMethod(label: "Test", amount: 1)
            shippingMethod.identifier = "TestId"

            self.applePayPlugin.paymentAuthorizationViewController(PKPaymentAuthorizationViewController(), didSelectShippingMethod: shippingMethod, completion: { (StatusBarStateViewController, summaryItems: [PKPaymentSummaryItem]) -> Void in
                XCTAssertTrue(summaryItems.count == 1)
                let paymentSummaryItem = summaryItems[0]
                XCTAssertTrue(paymentSummaryItem.label == "test")
                XCTAssertTrue(paymentSummaryItem.amount == 1)
                expectation.fulfill()
            })

            let receivedMessage = receiver.receivedMessage as? EventMessage
            XCTAssertTrue(receivedMessage != nil)
            XCTAssertTrue(receivedMessage!.eventName == "didSelectShippingMethod")
            self.applePayPlugin.didSelectShippingMethodCompletion(receivedMessage!.params["completionId"] as! NSNumber, authorizationStatus: .Success, paymentSummaryItems: [PKPaymentSummaryItem(label: "test", amount: 1)], respond: self.capturedResult.callback)
        })
    }

    func testParseAuthorizationStatus() {
        let success = ApplePayPlugin.parsePKPaymentAuthorizationStatus("Success", respond: capturedResult.callback)
        XCTAssertTrue(success == PKPaymentAuthorizationStatus.Success)

        let failure = ApplePayPlugin.parsePKPaymentAuthorizationStatus("Failure", respond: capturedResult.callback)
        XCTAssertTrue(failure == PKPaymentAuthorizationStatus.Failure)

        let invalidBillingPostalAddress = ApplePayPlugin.parsePKPaymentAuthorizationStatus("InvalidBillingPostalAddress", respond: capturedResult.callback)
        XCTAssertTrue(invalidBillingPostalAddress == PKPaymentAuthorizationStatus.InvalidBillingPostalAddress)

        let invalidShippingPostalAddress = ApplePayPlugin.parsePKPaymentAuthorizationStatus("InvalidShippingPostalAddress", respond: capturedResult.callback)
        XCTAssertTrue(invalidShippingPostalAddress == PKPaymentAuthorizationStatus.InvalidShippingPostalAddress)

        let invalidShippingContact = ApplePayPlugin.parsePKPaymentAuthorizationStatus("InvalidShippingContact", respond: capturedResult.callback)
        XCTAssertTrue(invalidShippingContact == PKPaymentAuthorizationStatus.InvalidShippingContact)

        let invalidAuthorizationStatus = ApplePayPlugin.parsePKPaymentAuthorizationStatus("DoesNotExist", respond: capturedResult.callback)
        XCTAssertTrue(invalidAuthorizationStatus == PKPaymentAuthorizationStatus.Failure)
    }

    func testParsePaymentSummaryItems() {
        let singleEntrySummaryItems = [["label": "Label", "amount": 500]]
        let singlePaymentItem = ApplePayPlugin.parsePaymentSummaryItems(singleEntrySummaryItems, respond: capturedResult.callback)
        AssertNotNil(singlePaymentItem)
        XCTAssertTrue(singlePaymentItem!.count == 1)
        XCTAssertTrue(singlePaymentItem![0].label == "Label")
        XCTAssertTrue(singlePaymentItem![0].amount == 5.0)

        let multipleEntrySummaryItems = [["label": "TestLabel1", "amount": 100], ["label": "TestLabel2", "amount": 200]]
        let multiplePaymentItems = ApplePayPlugin.parsePaymentSummaryItems(multipleEntrySummaryItems, respond: capturedResult.callback)
        AssertNotNil(multiplePaymentItems)
        XCTAssertTrue(multiplePaymentItems!.count == 2)
        XCTAssertTrue(multiplePaymentItems![0].label == "TestLabel1")
        XCTAssertTrue(multiplePaymentItems![0].amount == 1.0)
        XCTAssertTrue(multiplePaymentItems![1].label == "TestLabel2")
        XCTAssertTrue(multiplePaymentItems![1].amount == 2.0)

        let invalidSummaryItems = [["payment_label": "TestLabel1", "amount": 100], ["amount": 200]]
        let invalidPaymentItems = ApplePayPlugin.parsePaymentSummaryItems(invalidSummaryItems, respond: capturedResult.callback)
        AssertNil(invalidPaymentItems)
    }

    func testParseShippingMethodItems() {
        let rawSingleEntryShippingItems = [["label": "Label", "amount": 500, "identifier": "TestIdentifier"]]
        let singleShippingItem = ApplePayPlugin.parseShippingMethods(rawSingleEntryShippingItems, respond: capturedResult.callback)
        AssertNotNil(singleShippingItem)
        XCTAssertTrue(singleShippingItem!.count == 1)
        XCTAssertTrue(singleShippingItem![0].label == "Label")
        XCTAssertTrue(singleShippingItem![0].amount == 5.0)
        XCTAssertTrue(singleShippingItem![0].identifier == "TestIdentifier")

        let rawMultipleEntryShippingItems = [["label": "TestLabel1", "amount": 100, "identifier": "TestIdentifier1"], ["label": "TestLabel2", "amount": 200, "identifier": "TestIdentifier2"]]
        let multipleShippingItems = ApplePayPlugin.parseShippingMethods(rawMultipleEntryShippingItems, respond: capturedResult.callback)
        AssertNotNil(multipleShippingItems)
        XCTAssertTrue(multipleShippingItems!.count == 2)
        XCTAssertTrue(multipleShippingItems![0].label == "TestLabel1")
        XCTAssertTrue(multipleShippingItems![0].amount == 1.0)
        XCTAssertTrue(multipleShippingItems![0].identifier == "TestIdentifier1")
        XCTAssertTrue(multipleShippingItems![1].label == "TestLabel2")
        XCTAssertTrue(multipleShippingItems![1].amount == 2.0)
        XCTAssertTrue(multipleShippingItems![1].identifier == "TestIdentifier2")

        let rawInvalidShippingItems = [["label": "TestLabel1", "amount": 100], ["amount": 200]]
        let invalidShippingItems = ApplePayPlugin.parseShippingMethods(rawInvalidShippingItems, respond: capturedResult.callback)
        AssertNil(invalidShippingItems)
    }

    func testParseMerchantCapabilities() {
        let rawSingleMerchantCapability = ["3DS"]
        let singleMerchantCapability = ApplePayPlugin.parseMerchantCapabilities(rawSingleMerchantCapability, respond: capturedResult.callback)
        AssertNotNil(singleMerchantCapability)
        XCTAssertTrue(singleMerchantCapability!.contains(.Capability3DS))

        let rawMultipleMerchantCapabilities = ["3DS", "EMV"]
        let multipleMerchantCapabilities = ApplePayPlugin.parseMerchantCapabilities(rawMultipleMerchantCapabilities, respond: capturedResult.callback)
        AssertNotNil(multipleMerchantCapabilities)
        XCTAssertTrue(multipleMerchantCapabilities!.contains(.Capability3DS))
        XCTAssertTrue(multipleMerchantCapabilities!.contains(.CapabilityEMV))

        let rawInvalidMerchantCapability = ["Won't work."]
        let invalidMerchantCapability = ApplePayPlugin.parseMerchantCapabilities(rawInvalidMerchantCapability, respond: capturedResult.callback)
        AssertNil(invalidMerchantCapability)
    }

    func testParseRequiredAddressFields() {
        let rawSingleRequiredAddressField = ["PostalAddress"]
        let singleRequiredAddressField = ApplePayPlugin.parseRequiredAddressFields(rawSingleRequiredAddressField, respond: capturedResult.callback)
        AssertNotNil(singleRequiredAddressField)
        XCTAssertTrue(singleRequiredAddressField!.contains(.PostalAddress))

        let rawMultipleRequiredAddressFields = ["PostalAddress", "Phone"]
        let multipleRequiredAddressFields = ApplePayPlugin.parseRequiredAddressFields(rawMultipleRequiredAddressFields, respond: capturedResult.callback)
        AssertNotNil(multipleRequiredAddressFields)
        XCTAssertTrue(multipleRequiredAddressFields!.contains(.PostalAddress))
        XCTAssertTrue(multipleRequiredAddressFields!.contains(.Phone))

        let rawInvalidRequiredAddressField = ["Won't work."]
        let invalidRequiredAddressField = ApplePayPlugin.parseRequiredAddressFields(rawInvalidRequiredAddressField, respond: capturedResult.callback)
        AssertNil(invalidRequiredAddressField)
    }

    func testConvertShippingMethod() {
        let testShippingMethod = PKShippingMethod(label: "Test", amount: 5.0)

        let testConvertedShippingMethod = ApplePayPlugin.convertShippingMethod(testShippingMethod)
        XCTAssertTrue(testConvertedShippingMethod["label"] as? String == "Test")
        XCTAssertTrue(testConvertedShippingMethod["amount"] as? NSNumber == 5.0)

        testShippingMethod.identifier = "TestId"

        let testConvertedShippingMethod2 = ApplePayPlugin.convertShippingMethod(testShippingMethod)
        XCTAssertTrue(testConvertedShippingMethod2["identifier"] as? String == "TestId")

        testShippingMethod.detail = "TestDetail"

        let testConvertedShippingMethod3 = ApplePayPlugin.convertShippingMethod(testShippingMethod)
        XCTAssertTrue(testConvertedShippingMethod3["detail"] as? String == "TestDetail")
    }

    func testConvertABRecord() {
        let testABRecord1 = ApplePayPluginTests.createABRecord("123 Street", city: "Vancouver", state: "BC", postalCode: "V6B123", countryCode: "CA")

        guard let convertedTestABRecord1 = ApplePayPlugin.convertABRecord(testABRecord1) else {
            XCTFail()
            return
        }
        XCTAssertTrue(convertedTestABRecord1["address1"] as? String == "123 Street")
        XCTAssertTrue(convertedTestABRecord1["city"] as? String == "Vancouver")
        XCTAssertTrue(convertedTestABRecord1["state"] as? String == "BC")
        XCTAssertTrue(convertedTestABRecord1["postal"] as? String == "V6B123")
        XCTAssertTrue(convertedTestABRecord1["country"] as? String == "CA")

        let testABRecord2 = ApplePayPluginTests.createABRecord("123 Street", city: "Anaheim", state: "CA", postalCode: "90210", countryCode: "US")

        ABRecordSetValue(testABRecord2, kABPersonFirstNameProperty, "First", nil)
        ABRecordSetValue(testABRecord2, kABPersonLastNameProperty, "Last", nil)

        let phoneEntry: ABMultiValueRef = ABMultiValueCreateMutable(ABPropertyType(kABMultiStringPropertyType)).takeRetainedValue()
        ABMultiValueAddValueAndLabel(phoneEntry, "1-800-555-9999", kABHomeLabel, nil)
        ABRecordSetValue(testABRecord2, kABPersonPhoneProperty, phoneEntry, nil)

        guard let convertedTestABRecord2 = ApplePayPlugin.convertABRecord(testABRecord2) else {
            XCTFail()
            return
        }
        XCTAssertTrue(convertedTestABRecord2["address1"] as? String == "123 Street")
        XCTAssertTrue(convertedTestABRecord2["city"] as? String == "Anaheim")
        XCTAssertTrue(convertedTestABRecord2["state"] as? String == "CA")
        XCTAssertTrue(convertedTestABRecord2["postal"] as? String == "90210")
        XCTAssertTrue(convertedTestABRecord2["country"] as? String == "US")
        XCTAssertTrue(convertedTestABRecord2["firstName"] as? String == "First")
        XCTAssertTrue(convertedTestABRecord2["lastName"] as? String == "Last")
        XCTAssertTrue(convertedTestABRecord2["phone"] as? String == "1-800-555-9999")
    }

    func testConvertPKPayment() {
        let testPayment = MockPKPayment()

        let convertedTestPayment = ApplePayPlugin.convertPKPayment(testPayment)
        guard let convertedTestPaymentShippingAddress = convertedTestPayment["shippingAddress"] as? NSDictionary else {
            XCTFail()
            return
        }
        guard let convertedTestPaymentBillingAddress = convertedTestPayment["billingAddress"] as? NSDictionary else {
            XCTFail()
            return
        }
        guard let convertedTestPaymentShippingMethod = convertedTestPayment["shippingMethod"] as? NSDictionary else {
            XCTFail()
            return
        }
        guard let convertedTestPaymentToken = convertedTestPayment["token"] as? NSDictionary else {
            XCTFail()
            return
        }

        XCTAssertTrue(convertedTestPaymentBillingAddress["address1"] as? String == "555 Yellow St")
        XCTAssertTrue(convertedTestPaymentBillingAddress["city"] as? String == "Anaheim")
        XCTAssertTrue(convertedTestPaymentBillingAddress["state"] as? String == "CA")
        XCTAssertTrue(convertedTestPaymentBillingAddress["postal"] as? String == "90210")
        XCTAssertTrue(convertedTestPaymentBillingAddress["country"] as? String == "US")
        XCTAssertTrue(convertedTestPaymentShippingAddress["address1"] as? String == "")
        XCTAssertTrue(convertedTestPaymentShippingAddress["city"] as? String == "Vancouver")
        XCTAssertTrue(convertedTestPaymentShippingAddress["state"] as? String == "BC")
        XCTAssertTrue(convertedTestPaymentShippingAddress["postal"] as? String == "V6B")
        XCTAssertTrue(convertedTestPaymentShippingAddress["country"] as? String == "CA")
        XCTAssertTrue(convertedTestPaymentShippingMethod["label"] as? String == "Test")
        XCTAssertTrue(convertedTestPaymentShippingMethod["amount"] as? NSNumber == 5.0)
        XCTAssertTrue(convertedTestPaymentToken["paymentInstrumentName"] as? String == "paymentInstrumentName")
        XCTAssertTrue(convertedTestPaymentToken["paymentNetwork"] as? String == "paymentNetwork")
        XCTAssertTrue(convertedTestPaymentToken["transactionIdentifier"] as? String == "transactionIdentifier")

        guard let paymentDataNSData = String("paymentData").dataUsingEncoding(NSUTF8StringEncoding) else {
            XCTFail()
            return
        }
        let base64String = paymentDataNSData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions())
        XCTAssertTrue(convertedTestPaymentToken["paymentData"] as? String == base64String)
    }*/

    class MockPKPaymentToken: PKPaymentToken {
        override var paymentInstrumentName: String {
            return "paymentInstrumentName"
        }
        override var paymentNetwork: String {
            return "paymentNetwork"
        }
        override var transactionIdentifier: String {
            return "transactionIdentifier"
        }
        override var paymentData: Data {
            return String("paymentData").data(using: .utf8)!
        }
    }

    class MockPKPayment: PKPayment {
        override var shippingContact: PKContact? {
            let contact = PKContact()
            let address = CNMutablePostalAddress()
            address.city = "Vancouver"
            address.state = "BC"
            address.street = ""
            address.postalCode = "V6B"
            address.isoCountryCode = "CA"
            contact.postalAddress = address
            return contact
        }

        override var billingContact: PKContact? {
            let contact = PKContact()
            let address = CNMutablePostalAddress()
            address.city = "Anaheim"
            address.state = "CA"
            address.street = "555 Yellow St"
            address.postalCode = "90210"
            address.isoCountryCode = "US"
            contact.postalAddress = address
            return contact
        }

        override var shippingMethod: PKShippingMethod? {
            return PKShippingMethod(label: "Test", amount: 5)
        }

        override var token: PKPaymentToken {
            return MockPKPaymentToken()
        }
    }
}
