import json
import random
from threading import Thread
from typing import Optional

import pytest
from assertpy import assert_that
from messagebus import (
    Event,
    Message,
)
from msgbus import (
    FM_STRING,
    MBContext,
    MBMessage,
    MBService,
    TK_SYS_ACK,
    TK_SYS_HEALTH_CHECK,
)


def test_published_event_is_received_on_subscriber(
    publisher_mb_context,
    subscriber_mb_context,
    hv_service,
):
    subject = "MY_EVENT"
    event_type = "MY_TYPE"
    event_data = "EVENT_DATA"

    def publisher_thread():
        publisher_mb_context.MB_EvtSend(
            hv_service=hv_service,
            subject=subject,
            type=event_type,
            xml=event_data,
            synchronous=False,
        )

    subscriber_mb_context.MB_EvtSubscribe(
        hv_service=hv_service,
        subject=subject,
    )

    t = Thread(target=publisher_thread)
    t.start()

    event = subscriber_mb_context.MB_GetMessage()
    event = Event.build_from_message(
        message=Message(
            token=event.token,
            data_type=event.format,
            data=event.data,
        ),
    )
    assert_that(event.subject).is_equal_to(subject)
    assert_that(event.event_type).is_equal_to(event_type)
    assert_that(event.data).is_equal_to(event_data.encode())
    assert_that(event.synchronous).is_false()

    t.join()


def test_reply_from_sync_event_is_received_on_publisher(
    publisher_mb_context,
    subscriber_mb_context,
    hv_service,
):
    subject = "MY_EVENT"
    event_type = "MY_TYPE"
    event_data = "EVENT_DATA"
    reply_token = TK_SYS_ACK
    reply_data = b"REPLY_DATA"
    reply_format = FM_STRING
    received_reply: Optional[MBMessage] = None

    def publisher_thread():
        nonlocal received_reply
        received_reply = publisher_mb_context.MB_EvtSend(
            hv_service=hv_service,
            subject=subject,
            type=event_type,
            xml=event_data,
            synchronous=True,
            timeout=10000000,
        )

    subscriber_mb_context.MB_EvtSubscribe(
        hv_service=hv_service,
        subject=subject,
    )

    t = Thread(target=publisher_thread)
    t.start()

    mb_message_event = subscriber_mb_context.MB_GetMessage()
    event = Event.build_from_message(
        message=Message(
            token=mb_message_event.token,
            data_type=mb_message_event.format,
            data=mb_message_event.data,
        ),
    )
    assert_that(event.subject).is_equal_to(subject)
    assert_that(event.event_type).is_equal_to(event_type)
    assert_that(event.data).is_equal_to(event_data.encode())
    assert_that(event.synchronous).is_true()

    mb_message_event.token = reply_token
    subscriber_mb_context.MB_ReplyMessage(
        message=mb_message_event,
        format=reply_format,
        data=reply_data,
    )

    t.join()
    assert_that(received_reply).is_not_none()
    assert_that(received_reply.token).is_equal_to(reply_token)
    assert_that(received_reply.data).is_equal_to(reply_data)
    assert_that(received_reply.format).is_equal_to(reply_format)


def test_health_check(publisher_mb_context, hv_service):
    reply = publisher_mb_context.MB_SendMessage(
        destination=hv_service,
        token=TK_SYS_HEALTH_CHECK,
    )

    assert_that(reply.token).is_equal_to(TK_SYS_ACK)
    reply_dict = json.loads(reply.data.decode())
    assert_that(reply_dict).is_equal_to({"status": "OK"})


@pytest.fixture
def publisher_mb_context():
    publisher_mb_context = MBContext(
        my_service=None,
        name="Publisher",
    )

    yield publisher_mb_context

    publisher_mb_context.MB_Finalize()


@pytest.fixture
def subscriber_mb_context(subscriber_service):
    mb_context = MBContext(
        my_service=subscriber_service,
        name="Subscriber",
    )

    yield mb_context

    mb_context.MB_Finalize()


@pytest.fixture
def subscriber_service():
    random_port = random.randint(30000, 65535)
    return MBService(
        address="127.0.0.1",
        port=random_port,
    )
