from AWSIoTPythonSDK.core.protocol.mqtt_core import MqttCore
from AWSIoTPythonSDK.core.shadow.shadowManager import shadowManager
try:
    from mock import MagicMock
except:
    from unittest.mock import MagicMock
try:
    from mock import NonCallableMagicMock
except:
    from unittest.mock import NonCallableMagicMock
try:
    from mock import call
except:
    from unittest.mock import call
import pytest


DUMMY_SHADOW_NAME = "CoolShadow"
DUMMY_PAYLOAD = "{}"

OP_SHADOW_GET = "get"
OP_SHADOW_UPDATE = "update"
OP_SHADOW_DELETE = "delete"
OP_SHADOW_DELTA = "delta"
OP_SHADOW_TROUBLE_MAKER = "not_a_valid_shadow_aciton_name"

DUMMY_SHADOW_TOPIC_PREFIX = "$aws/things/" + DUMMY_SHADOW_NAME + "/shadow/"
DUMMY_SHADOW_TOPIC_GET = DUMMY_SHADOW_TOPIC_PREFIX + "get"
DUMMY_SHADOW_TOPIC_GET_ACCEPTED = DUMMY_SHADOW_TOPIC_GET + "/accepted"
DUMMY_SHADOW_TOPIC_GET_REJECTED = DUMMY_SHADOW_TOPIC_GET + "/rejected"
DUMMY_SHADOW_TOPIC_UPDATE = DUMMY_SHADOW_TOPIC_PREFIX + "update"
DUMMY_SHADOW_TOPIC_UPDATE_ACCEPTED = DUMMY_SHADOW_TOPIC_UPDATE + "/accepted"
DUMMY_SHADOW_TOPIC_UPDATE_REJECTED = DUMMY_SHADOW_TOPIC_UPDATE + "/rejected"
DUMMY_SHADOW_TOPIC_UPDATE_DELTA = DUMMY_SHADOW_TOPIC_UPDATE + "/delta"
DUMMY_SHADOW_TOPIC_DELETE = DUMMY_SHADOW_TOPIC_PREFIX + "delete"
DUMMY_SHADOW_TOPIC_DELETE_ACCEPTED = DUMMY_SHADOW_TOPIC_DELETE + "/accepted"
DUMMY_SHADOW_TOPIC_DELETE_REJECTED = DUMMY_SHADOW_TOPIC_DELETE + "/rejected"


class TestShadowManager:

    def setup_method(self, test_method):
        self.mock_mqtt_core = MagicMock(spec=MqttCore)
        self.shadow_manager = shadowManager(self.mock_mqtt_core)

    def test_basic_shadow_publish(self):
        self.shadow_manager.basicShadowPublish(DUMMY_SHADOW_NAME, OP_SHADOW_GET, DUMMY_PAYLOAD)
        self.shadow_manager.basicShadowPublish(DUMMY_SHADOW_NAME, OP_SHADOW_UPDATE, DUMMY_PAYLOAD)
        self.shadow_manager.basicShadowPublish(DUMMY_SHADOW_NAME, OP_SHADOW_DELETE, DUMMY_PAYLOAD)
        self.mock_mqtt_core.publish.assert_has_calls([call(DUMMY_SHADOW_TOPIC_GET, DUMMY_PAYLOAD, 0, False),
                                                      call(DUMMY_SHADOW_TOPIC_UPDATE, DUMMY_PAYLOAD, 0, False),
                                                      call(DUMMY_SHADOW_TOPIC_DELETE, DUMMY_PAYLOAD, 0, False)])

    def test_basic_shadow_subscribe(self):
        callback = NonCallableMagicMock()
        self.shadow_manager.basicShadowSubscribe(DUMMY_SHADOW_NAME, OP_SHADOW_GET, callback)
        self.shadow_manager.basicShadowSubscribe(DUMMY_SHADOW_NAME, OP_SHADOW_UPDATE, callback)
        self.shadow_manager.basicShadowSubscribe(DUMMY_SHADOW_NAME, OP_SHADOW_DELETE, callback)
        self.shadow_manager.basicShadowSubscribe(DUMMY_SHADOW_NAME, OP_SHADOW_DELTA, callback)
        self.mock_mqtt_core.subscribe.assert_has_calls([call(DUMMY_SHADOW_TOPIC_GET_ACCEPTED, 0, callback),
                                                        call(DUMMY_SHADOW_TOPIC_GET_REJECTED, 0, callback),
                                                        call(DUMMY_SHADOW_TOPIC_UPDATE_ACCEPTED, 0, callback),
                                                        call(DUMMY_SHADOW_TOPIC_UPDATE_REJECTED, 0, callback),
                                                        call(DUMMY_SHADOW_TOPIC_DELETE_ACCEPTED, 0, callback),
                                                        call(DUMMY_SHADOW_TOPIC_DELETE_REJECTED, 0, callback),
                                                        call(DUMMY_SHADOW_TOPIC_UPDATE_DELTA, 0, callback)])

    def test_basic_shadow_unsubscribe(self):
        self.shadow_manager.basicShadowUnsubscribe(DUMMY_SHADOW_NAME, OP_SHADOW_GET)
        self.shadow_manager.basicShadowUnsubscribe(DUMMY_SHADOW_NAME, OP_SHADOW_UPDATE)
        self.shadow_manager.basicShadowUnsubscribe(DUMMY_SHADOW_NAME, OP_SHADOW_DELETE)
        self.shadow_manager.basicShadowUnsubscribe(DUMMY_SHADOW_NAME, OP_SHADOW_DELTA)
        self.mock_mqtt_core.unsubscribe.assert_has_calls([call(DUMMY_SHADOW_TOPIC_GET_ACCEPTED),
                                                          call(DUMMY_SHADOW_TOPIC_GET_REJECTED),
                                                          call(DUMMY_SHADOW_TOPIC_UPDATE_ACCEPTED),
                                                          call(DUMMY_SHADOW_TOPIC_UPDATE_REJECTED),
                                                          call(DUMMY_SHADOW_TOPIC_DELETE_ACCEPTED),
                                                          call(DUMMY_SHADOW_TOPIC_DELETE_REJECTED),
                                                          call(DUMMY_SHADOW_TOPIC_UPDATE_DELTA)])

    def test_unsupported_shadow_action_name(self):
        with pytest.raises(TypeError):
            self.shadow_manager.basicShadowUnsubscribe(DUMMY_SHADOW_NAME, OP_SHADOW_TROUBLE_MAKER)