//
//  MessagingTests.swift
//  Astro
//
//  Created by Mark Sandstrom on 4/22/15.
//  Copyright (c) 2015 Mobify Research & Development Inc. All rights reserved.
//

import XCTest
@testable import Astro

struct TestMessageFactory {
    static func createMessage(_ to: String = "destination", from: String = "source") -> Message {
        return Message(to: to, from: from)
    }

    static func createRpcRequest(_ to: String = "destination", from: String = "source", id: RPCMessageID = 1, method: String = "method", params: JSONObject = JSONObject()) -> RPCRequest {
        return RPCRequest(to: to, from: from, id: id, method: method, params: params)
    }

    static func createJsRpcRequest(_ to: String = "destination", from: String = "source", id: RPCMessageID = 1, method: String = "method", params: JSONObject = JSONObject()) -> JSRPCRequest {
        return JSRPCRequest(to: to, from: from, id: id, method: method, params: params)
    }

    static func createRpcResponse(_ to: String = "destination", from: String = "source", id: RPCMessageID = 1, rpcMethodResult: RPCMethodResult = .result(JSONObject())) -> RPCResponse {
        return RPCResponse(to: to, from: from, id: id, rpcMethodResult: rpcMethodResult)
    }

    static func createEventMessage(_ to: String = "destination", from: String = "source", eventName: String = "event", params: JSONObject = JSONObject()) -> EventMessage {
        return EventMessage(to: to, from: from, eventName: eventName, params: params)
    }
}

class TestAddressable: Addressable {
    let address: MessageAddress

    init(address: MessageAddress) {
        self.address = address
    }
}

class TestReceiver: Addressable, MessageReceiver {
    var receivedMessage: Message?
    let address: MessageAddress

    init(address: MessageAddress) {
        self.address = address
    }

    func receive(_ message: Message) {
        receivedMessage = message
    }
}

class TestReceiveMessageBlockReceiver: MessageReceiver {
    let receiveMessageBlock: (Message) -> Void

    init(block: @escaping (Message) -> Void) {
        receiveMessageBlock = block
    }

    func receive(_ message: Message) {
        receiveMessageBlock(message)
    }
}

class TestBaseMessageReceiver: BaseMessageReceiver, Addressable {
    @objc let address: MessageAddress

    @objc init(address: MessageAddress) {
        self.address = address
    }
}

class TestRequestCapturingRpcReceiver: TestBaseMessageReceiver {
    var rpcMessage: RPCMessage?

    override func receive(_ message: RPCRequest) {
        self.rpcMessage = message
    }

    override func receive(_ message: RPCResponse) {
        self.rpcMessage = message
    }
}

class TestRpcReceiverResponder: TestBaseMessageReceiver {
    var result: RPCMethodResult?

    override func callRPCMethod(_ name: String, params: JSONObject, respond: @escaping RPCMethodCallback) {
        if let result = self.result {
            respond(result)
        }
    }
}

class TestBaseShimMessageReceiver: BaseMessageReceiver {
    @objc var method1Called = false

    // @RpcMethod
    func method1(_ respond: RPCMethodCallback) {
        method1Called = true
    }
}

class TestInheritedShimMessageReceiver: TestBaseShimMessageReceiver {
    @objc var method2Called = false

    // @RpcMethod
    func method2(_ respond: RPCMethodCallback) {
        method2Called = true
    }
}

class MessagingTests: AstroTestCase {
    var messageBus: MessageBus!

    override func setUp() {
        messageBus = MessageBus()
    }

    // TODO: We need to revisit this once we get events in place.  Do we want to block listening on "RPC addresses"?
    func testAMessageCanBeSentToAReceiver() {
        let receiver = TestReceiver(address: "destination")
        let message = TestMessageFactory.createMessage("destination")

        messageBus.listen(on: "destination", receiver: receiver)
        messageBus.send(message)

        XCTAssertTrue(message === receiver.receivedMessage)
    }

    func testAReceiverCanRegisterWithTheBus() {
        let receiver = TestReceiver(address: "destination")
        messageBus.register(receiver)

        let message = TestMessageFactory.createMessage("destination")

        messageBus.send(message)

        XCTAssertTrue(message === receiver.receivedMessage)
    }

    func testIsReceiverListeningToAddressReturnsTrueIfReceiverListeningToAddress() {
        let receiver = TestReceiver(address: "destination")
        messageBus.listen(on: "destination", receiver: receiver)

        XCTAssertTrue(messageBus.isListening(toAddress: "destination", receiver: receiver))
    }

    func testIsReceiverListeningToAddressReturnsFalseIfReceiverNotListeningToAddress() {
        let receiver = TestReceiver(address: "destination")

        XCTAssertFalse(messageBus.isListening(toAddress: "destination", receiver: receiver))
    }

    func testUnsentMessageHasNilMessageBus() {
        let message = TestMessageFactory.createMessage()
        XCTAssertNil(message.messageBus)
    }

    func testSentMessageHasAttachedMessageBus() {
        let message = TestMessageFactory.createMessage()
        messageBus.send(message)

        XCTAssertTrue(message.messageBus === messageBus)
    }

    func testMessagesSentToAnAddressWithoutAReceiverAreNotReceived() {
        let receiver = TestReceiver(address: "destination")
        let message = TestMessageFactory.createMessage("unknown_to")

        messageBus.register(receiver)
        messageBus.send(message)

        XCTAssertNil(receiver.receivedMessage)
    }

    func testMessagesAreReceivedOnTheMainThread() {
        let expectation = self.expectation(description: "Message received")

        let receiver = TestReceiveMessageBlockReceiver { _ in
            XCTAssertTrue(Thread.isMainThread)
            expectation.fulfill()
        }

        messageBus.listen(on: "destination", receiver: receiver)
        let message = TestMessageFactory.createMessage("destination")

        DispatchQueue.global(qos: .background).async {
            self.messageBus.send(message)

        }

        self.waitForExpectations(timeout: 2.5, handler: nil)
    }

    func testRpcMessageCallsReceiveRpcRequestWithMessage() {
        let receiver = TestRequestCapturingRpcReceiver(address: "destination")
        messageBus.register(receiver)

        let rpcMessage = TestMessageFactory.createRpcRequest("destination")
        messageBus.send(rpcMessage)

        XCTAssert(receiver.rpcMessage === rpcMessage)
    }

    func testRpcMessageIsOnlyDeliveredToAnAddressableWithMatchingAddress() {
        let receiver1 = TestRequestCapturingRpcReceiver(address: "destination")
        let receiver2 = TestRequestCapturingRpcReceiver(address: "another-destination")

        messageBus.register(receiver1)
        messageBus.listen(on: "destination", receiver: receiver2)

        let rpcMessage = TestMessageFactory.createRpcRequest("destination")
        messageBus.send(rpcMessage)

        XCTAssertNotNil(receiver1.rpcMessage)
        XCTAssertNil(receiver2.rpcMessage)
    }

    func testAMessageSentToAnAddressIsReceivedByAllReceivers() {
        let receiver1 = TestReceiver(address: "destination")
        messageBus.listen(on: "destination", receiver: receiver1)

        let receiver2 = TestReceiver(address: "destination")
        messageBus.listen(on: "destination", receiver: receiver2)

        let message = TestMessageFactory.createMessage("destination")
        messageBus.send(message)

        XCTAssert(receiver1.receivedMessage === message)
        XCTAssert(receiver2.receivedMessage === message)
    }

    func testAnRpcResponseIsAddressedToTheSenderOfTheCorrespondingRpcRequest() {
        let request = TestMessageFactory.createRpcRequest("destination", from: "source")
        let response = request.createResponse(.result("ignored"))

        XCTAssertEqual(request.from, response.to)
    }

    func testRpcReceiverReturnsResponseToSender() {
        let source = TestRequestCapturingRpcReceiver(address: "source")
        messageBus.register(source)

        let receiver = TestRpcReceiverResponder(address: "destination")
        messageBus.register(receiver)

        receiver.result = .result("data")
        let request = TestMessageFactory.createRpcRequest("destination", from: "source")
        messageBus.send(request)

        let expectation = self.expectation(description: "Rpc response received")

        if let response = source.rpcMessage as? RPCResponse {
            XCTAssert(response.result as? String == "data")
            expectation.fulfill()
        }

        self.waitForExpectations(timeout: 0, handler: nil)
    }

    // Mark: MessageFactory tests
    func testRpcRequestCanBeCreatedFromBridgeMessage() {
        expectAssertion { expectation in
            let jsonObject: JSONObject = ["id": 1, "payload": ["method": "rpcMethod", "params": JSONObject()]]
            let bridgeMessage = BridgeMessage(address: "destination", jsonObject: jsonObject)
            let message = WebBridgeUtils.message(from: bridgeMessage, owner: TestAddressable(address: "source"))
            if let message = message as? RPCRequest {
                XCTAssertEqual(message.method, "rpcMethod")
                XCTAssertEqual(message.params as NSDictionary, JSONObject() as NSDictionary)
                expectation.fulfill()
            }
        }
    }

    func testJsRpcRequestCanBeCreatedFromBridgeMessage() {
        expectAssertion { expectation in
            let jsonObject: JSONObject = ["id": 1, "isJsRpc": true, "payload": ["method": "jsRpcMethod", "params": JSONObject()]]
            let bridgeMessage = BridgeMessage(address: "destination", jsonObject: jsonObject)
            let message = WebBridgeUtils.message(from: bridgeMessage, owner: TestAddressable(address: "source"))
            if let message = message as? JSRPCRequest {
                XCTAssertEqual(message.method, "jsRpcMethod")
                XCTAssertEqual(message.params as NSDictionary, JSONObject() as NSDictionary)
                expectation.fulfill()
            }
        }
    }

    func testRpcReponseWithResultCanBeCreatedFromBridgeMessage() {
        expectAssertion { expectation in
            let jsonObject: JSONObject = ["id": 1, "payload": ["result": "some result", "error": NSNull()]]
            let bridgeMessage = BridgeMessage(address: "destination", jsonObject: jsonObject)
            let message = WebBridgeUtils.message(from: bridgeMessage, owner: TestAddressable(address: "source"))
            if let message = message as? RPCResponse {
                XCTAssertEqual(message.result as? String, "some result")
                expectation.fulfill()
            }
        }
    }

    func testRpcReponseWithErrorCanBeCreatedFromBridgeMessage() {
        expectAssertion { expectation in
            let jsonObject: JSONObject = ["id": 1, "payload": ["result": NSNull(), "error": "a wild error"]]
            let bridgeMessage = BridgeMessage(address: "destination", jsonObject: jsonObject)
            let message = WebBridgeUtils.message(from: bridgeMessage, owner: TestAddressable(address: "source"))
            guard let rpcResponse = message as? RPCResponse else {
                XCTFail("Message was not an RPCResponse")
                return
            }
            guard let error = rpcResponse.payload["error"] as? JSONObject else {
                XCTFail("Error is not a JSONObject; found \(rpcResponse.payload["error"]  ?? "No error found")")
                return
            }
            XCTAssertEqual(error["message"] as? String, "a wild error")
            expectation.fulfill()
        }
    }

    func testEventMessageCanBeCreatedFromBridgeMessage() {
        expectAssertion { expectation in
            let jsonObject: JSONObject = ["payload": ["eventName": "event", "params": ["key": "value"]]]
            let bridgeMessage = BridgeMessage(address: "destination", jsonObject: jsonObject)
            let message = WebBridgeUtils.message(from: bridgeMessage, owner: TestAddressable(address: "source"))
            if let message = message as? EventMessage {
                XCTAssertEqual(message.eventName, "event")
                XCTAssertEqual(message.params as! [String: String], ["key": "value"])
                expectation.fulfill()
            }
        }
    }

    func testAnErrorIsReturnedForInvalidMessageJson() {
        let invalidMessageJson: [String: JSONObject] = [
            "missing everything": JSONObject(),
            "missing payload": [
                "id": "1"
            ],
            "invalid payload type": [
                "id": "1",
                "payload": "junk"
            ],
            // RPCMessage
            "missing rpc id": [
                "payload": JSONObject()
            ],
            "invalid rpc id type": [
                "id": "1",
                "payload": JSONObject()
            ],
            "missing rpc params": [
                "method": "method"
            ],
            "invalid rpc params type": [
                "params": "invalid",
                "method": "method"
            ],
            "missing rpc method": [
                "payload": JSONObject()
            ],
            "invalid rpc method type": [
                "params": JSONObject(),
                "method": 0
            ],
            // EventMessage
            "missing event eventName": [
                "payload": ["params": JSONObject()]
            ],
            "invalid event eventName type": [
                "payload": ["eventName": 1, "params": JSONObject()]
            ],
            "missing event params": [
                "eventName": ["eventName": "event"]
            ],
            "invalid event params type": [
                "payload": ["eventName": "event", "params": "invalid"]
            ]
        ]

        for (testName, messageJson) in invalidMessageJson {
            let bridgeMessage = BridgeMessage(address: "destination", jsonObject: messageJson)
            let message = WebBridgeUtils.message(from: bridgeMessage, owner: TestAddressable(address: "source"))
            XCTAssertNil(message, testName)
        }
    }

    func testJsToNativeAddressTranslation() {
        let addresses = [
            ("self", "destination"),
            ("self:events", "destination:events"),
            ("another_destination", "another_destination")
        ]

        for (address, expectedAddress) in addresses {
            let owner = TestAddressable(address: "destination")

            let translatedAddress = WebBridgeUtils.jsAddressToNativeAddress(address, owner: owner)

            XCTAssertEqual(translatedAddress, expectedAddress)
        }
    }

    func testNativeToJsAddressTranslation() {
        let addresses = [
            ("destination", "self"),
            ("destination:events", "self:events"),
            ("another_destination", "another_destination")
        ]

        for (address, expectedAddress) in addresses {
            let owner = TestAddressable(address: "destination")

            let translatedAddress = WebBridgeUtils.nativeAddressToJsAddress(address, owner: owner)

            XCTAssertEqual(translatedAddress, expectedAddress)
        }
    }
}
