from uuid import UUID, uuid4

import pytest

from hololinked.core.zmq.message import (
    ERROR,
    EXIT,
    HANDSHAKE,
    INVALID_MESSAGE,
    OPERATION,
    REPLY,
    TIMEOUT,
    EventHeader,
    EventMessage,
    PreserializedData,
    RequestHeader,
    RequestMessage,
    ResponseHeader,
    ResponseMessage,
    SerializableData,
)
from hololinked.serializers.serializers import Serializers


try:
    from .conftest import AppIDs
except ImportError:
    from conftest import AppIDs


def validate_request_message(request_message: RequestMessage, app_ids: AppIDs) -> None:
    """call this method to validate request message"""
    # req. 1. check message ID is a UUID
    assert isinstance(request_message.id, UUID) or isinstance(UUID(request_message.id, version=4), UUID)
    # req. 2. generated byte array must confine to predefined length (which is readonly & fixed)
    assert len(request_message.byte_array) == request_message.length
    # req. 3. receiver which must be the server ID
    assert request_message.receiver_id == app_ids.server_id
    # req. 4. sender_id is the client ID
    assert request_message.sender_id == app_ids.client_id
    # req. 5. all indices of byte array are bytes
    for obj in request_message.byte_array:
        assert isinstance(obj, bytes)
    # req. 6. check that header is correct type (RequestHeader dataclass/struct)
    assert isinstance(request_message.header, RequestHeader)
    # req. 7 check that body is correct type (list of SerializableData and PreserializedData)
    assert isinstance(request_message.body, list)
    assert len(request_message.body) == 2
    assert isinstance(request_message.body[0], SerializableData)
    assert isinstance(request_message.body[1], PreserializedData)


def validate_response_message(response_message: ResponseMessage, app_ids: AppIDs) -> None:
    """call this method to validate response message"""

    # check message ID is a UUID
    assert isinstance(response_message.id, UUID) or isinstance(UUID(response_message.id, version=4), UUID)
    # check message length
    assert len(response_message.byte_array) == response_message.length
    # check receiver which must be the client
    assert response_message.receiver_id == app_ids.client_id
    # sender_id is not set before sending message on the socket
    assert response_message.sender_id == app_ids.server_id
    # check that all indices are bytes
    for obj in response_message.byte_array:
        assert isinstance(obj, bytes)
    # check that header is correct type
    assert isinstance(response_message.header, ResponseHeader)
    # check that body is correct type
    assert isinstance(response_message.body, list)
    assert len(response_message.body) == 2
    assert isinstance(response_message.body[0], SerializableData)
    assert isinstance(response_message.body[1], PreserializedData)


def validate_event_message(event_message: EventMessage, app_ids: AppIDs) -> None:
    """call this method to validate event message"""

    # check message ID is a UUID
    assert isinstance(event_message.id, UUID) or isinstance(UUID(event_message.id, version=4), UUID)
    # check message length
    assert len(event_message.byte_array) == event_message.length
    # no receiver id for event message, only event id
    assert isinstance(event_message.event_id, str)
    # sender_id is not set before sending message on the socket
    assert event_message.sender_id == app_ids.server_id
    # check that all indices are bytes
    for obj in event_message.byte_array:
        assert isinstance(obj, bytes)
    # check that header is correct type
    assert isinstance(event_message.header, EventHeader)
    # check that body is correct type
    assert isinstance(event_message.body, list)
    assert len(event_message.body) == 2
    assert isinstance(event_message.body[0], SerializableData)
    assert isinstance(event_message.body[1], PreserializedData)


def test_01_request_message(app_ids: AppIDs) -> None:
    """test the request message"""

    # request messages types are OPERATION, HANDSHAKE & EXIT
    request_message = RequestMessage.craft_from_arguments(
        receiver_id=app_ids.server_id,
        sender_id=app_ids.client_id,
        thing_id=app_ids.thing_id,
        objekt="some_prop",
        operation="readproperty",
    )
    validate_request_message(request_message, app_ids)
    # check message type for the above craft_from_arguments method
    assert request_message.type == OPERATION

    request_message = RequestMessage.craft_with_message_type(
        receiver_id=app_ids.server_id, sender_id=app_ids.client_id, message_type=HANDSHAKE
    )
    validate_request_message(request_message, app_ids)
    # check message type for the above craft_with_message_type method
    assert request_message.type == HANDSHAKE

    request_message = RequestMessage.craft_with_message_type(
        receiver_id=app_ids.server_id, sender_id=app_ids.client_id, message_type=EXIT
    )
    validate_request_message(request_message, app_ids)
    # check message type for the above craft_with_message_type method
    assert request_message.type == EXIT


def test_02_response_message(app_ids: AppIDs) -> None:
    """test the response message"""

    # response messages types are HANDSHAKE, TIMEOUT, INVALID_MESSAGE, ERROR and REPLY
    response_message = ResponseMessage.craft_from_arguments(
        receiver_id=app_ids.client_id,
        sender_id=app_ids.server_id,
        message_type=HANDSHAKE,
        message_id=uuid4(),
    )
    validate_response_message(response_message, app_ids)
    # check message type for the above craft_with_message_type method
    assert response_message.type == HANDSHAKE

    response_message = ResponseMessage.craft_from_arguments(
        receiver_id=app_ids.client_id,
        sender_id=app_ids.server_id,
        message_type=TIMEOUT,
        message_id=uuid4(),
    )
    validate_response_message(response_message, app_ids)
    # check message type for the above craft_with_message_type method
    assert response_message.type == TIMEOUT

    response_message = ResponseMessage.craft_from_arguments(
        receiver_id=app_ids.client_id,
        sender_id=app_ids.server_id,
        message_type=INVALID_MESSAGE,
        message_id=uuid4(),
    )
    validate_response_message(response_message, app_ids)
    # check message type for the above craft_with_message_type method
    assert response_message.type == INVALID_MESSAGE

    response_message = ResponseMessage.craft_from_arguments(
        receiver_id=app_ids.client_id,
        sender_id=app_ids.server_id,
        message_type=ERROR,
        message_id=uuid4(),
        payload=SerializableData(Exception("test")),
    )
    validate_response_message(response_message, app_ids)
    assert response_message.type == ERROR
    assert isinstance(Serializers.json.loads(response_message._bytes[2]), dict)

    request_message = RequestMessage.craft_from_arguments(
        sender_id=app_ids.client_id,
        receiver_id=app_ids.server_id,
        thing_id=app_ids.thing_id,
        objekt="some_prop",
        operation="readProperty",
    )
    request_message._sender_id = app_ids.client_id  # will be done by craft_from_self
    response_message = ResponseMessage.craft_reply_from_request(
        request_message=request_message,
    )
    validate_response_message(response_message, app_ids)
    assert response_message.type == REPLY
    assert Serializers.json.loads(response_message._bytes[3]) is None  # INDEX_BODY = 3
    assert request_message.id == response_message.id


def test_03_event_message(app_ids: AppIDs) -> None:
    """test the event message"""
    event_message = EventMessage.craft_from_arguments(
        event_id="test-event",
        sender_id=app_ids.server_id,
        payload=SerializableData("test"),
        preserialized_payload=PreserializedData(b"test"),
    )
    validate_event_message(event_message, app_ids)


if __name__ == "__main__":
    pytest.main([__file__, "-v", "-s"])
