from unittest import mock

import pytest

from devices.models.sensor import ArmaSensor
from devices.services.sensor.enums import SystemMessage
from devices.services.sensor.rabbitmq import SensorManagement, SensorResponseException

SENSOR_DATA = {
    "name": "SENSOR_TEST",
    "ip": "192.168.56.103",
    "port": 5000,
    "type": 'firewall'
}

# each case contains the source message and the expected response
test_send_message_with_decode_success = [
    ('{"status": "ok"}', {'status': 'ok'}),
    ('["1","2"]', ["1", "2"]),
    ('{}', {})
]

test_send_message_with_decode_error = [
    ('', {'status': 'error',
          'detail': 'sensor doesnt return response'}),
    (b'', {'status': 'error',
           'detail': 'sensor doesnt return response'}),
    ([], {'status': 'error',
          'detail': 'sensor doesnt return response'}),
    ('body_from_sensor', {'status': 'error',
                          'detail': 'failed to decode sensor response to json'}),
    (b'body_from_sensor', {'status': 'error',
                           'detail': 'failed to decode sensor response to json'}),
]

test_send_message_without_decode = [
    ('', '{"status": "error", "detail": "sensor doesnt return response"}'),
    (b'', '{"status": "error", "detail": "sensor doesnt return response"}'),
    ([], '{"status": "error", "detail": "sensor doesnt return response"}'),
    ('body_from_sensor', 'body_from_sensor'),
    (b'bytes_from_sensor', b'bytes_from_sensor'),
]


def mock_pika(*args, **kwargs):
    class MockBlockingChannel:
        def basic_consume(self, *args, **kwargs): pass

        def basic_publish(self, *args, **kwargs): pass

        def exchange_declare(self, *args, **kwargs): pass

        def queue_declare(self, *args, **kwargs):
            class Queue:
                queue = 'callback_queue'

            class Method:
                method = Queue()

            return Method()

    class MockBlockingConnection:

        def __init__(self, *args, **kwargs): pass

        def close(self): pass

        def process_data_events(self, *args, **kwargs): pass

        def call_later(self, *args, **kwargs): pass

        def channel(self):
            return MockBlockingChannel()

    return MockBlockingConnection


@pytest.mark.unit
@pytest.mark.django_db
class TestSensorManagement:

    @pytest.fixture(autouse=True)
    def setup_tests(self):
        self.sensor = ArmaSensor.objects.create(**SENSOR_DATA)

    @mock.patch('pika.BlockingConnection', mock_pika())
    def test_send_message_to_all_without_response(self):
        service = SensorManagement()
        response = service.send_message()
        assert response is None

    @mock.patch('pika.BlockingConnection', mock_pika())
    def test_send_message_to_all_but_no_one_answer(self):
        service = SensorManagement(proceed=False)
        response = service.send_message(message_type=SystemMessage.ping, wait_response=True)
        assert response == ()

    @pytest.mark.parametrize('test_data', test_send_message_with_decode_error)
    @mock.patch('pika.BlockingConnection', mock_pika())
    def test_send_message_to_all_with_decode_error(self, test_data):
        service = SensorManagement(proceed=False)
        body = test_data[0]
        service._response_callback(None, None, None, body, True)
        response = service.send_message(message_type=SystemMessage.ping, wait_response=True)

        print(response)
        assert response[0] == test_data[1]
        assert len(response) == 1

    @pytest.mark.parametrize('test_data', test_send_message_with_decode_error)
    @mock.patch('pika.BlockingConnection', mock_pika())
    def test_send_message_to_sensor_with_decode_error(self, test_data: tuple):
        service = SensorManagement(proceed=False)

        body = test_data[0]
        service._response_callback(None, None, None, body, True)

        with pytest.raises(SensorResponseException) as exc:
            service.send_message(sensor=self.sensor, message_type=SystemMessage.ping, wait_response=True)
        assert exc.value.detail['status'] == test_data[1]['status']
        assert exc.value.detail['detail'] == test_data[1]['detail']

    @pytest.mark.parametrize('test_data', test_send_message_without_decode)
    @mock.patch('pika.BlockingConnection', mock_pika())
    def test_send_message_to_all_without_decode(self, test_data):
        service = SensorManagement(proceed=False)
        body = test_data[0]
        service._response_callback(None, None, None, body, False)
        response = service.send_message(message_type=SystemMessage.ping, wait_response=True)

        assert response[0] == test_data[1]
        assert len(response) == 1

    @pytest.mark.parametrize('test_data', test_send_message_without_decode)
    @mock.patch('pika.BlockingConnection', mock_pika())
    def test_send_message_to_sensor_without_decode(self, test_data: tuple):
        service = SensorManagement(proceed=False)

        body = test_data[0]
        service._response_callback(None, None, None, body, False)

        response = service.send_message(sensor=self.sensor, message_type=SystemMessage.ping, wait_response=True)

        assert response == test_data[1]

    @pytest.mark.parametrize('test_data', test_send_message_with_decode_success)
    @mock.patch('pika.BlockingConnection', mock_pika())
    def test_send_message_to_all_with_decode_success(self, test_data):
        service = SensorManagement(proceed=False)

        body = test_data[0]
        service._response_callback(None, None, None, body, True)

        response = service.send_message(message_type=SystemMessage.ping, wait_response=True)

        assert response[0] == test_data[1]
        assert len(response) == 1

    @pytest.mark.parametrize('test_data', test_send_message_with_decode_success)
    @mock.patch('pika.BlockingConnection', mock_pika())
    def test_send_message_to_sensor_with_decode_success(self, test_data):
        service = SensorManagement(proceed=False)
        body = test_data[0]
        service._response_callback(None, None, None, body, True)

        response = service.send_message(sensor=self.sensor, message_type=SystemMessage.ping, wait_response=True)

        assert response == test_data[1]
