from threading import Thread

import pytest
from assertpy import assert_that, fail
from messagebus import Event, Message

import msgbus
from msgbus import (
    MBContext,
    MBEasyContext,
    MBException,
    MBService,
    MBTimeout,
    SE_NOTFOUND,
    SE_SUCCESS,
    TK_CMP_GETDEPENDS,
    TK_CMP_GETSERVICES,
    TK_CMP_START,
    TK_SYS_ACK,
)

_SERVER_IP = "127.0.0.1"
_SERVER_PORT = 14590
_CLIENT_PORT = 14591
_HV_SIM_PORT = 15004
_COMP_PORT = 15005
_SERVICE_NAME = "easy-test-service"


@pytest.fixture
def server_and_client(hv_address, hv_port):
    hv_service = MBService(address=hv_address, port=hv_port)
    server = MBContext(
        my_service=MBService(address=_SERVER_IP, port=_SERVER_PORT),
        name="server",
    )
    client = MBEasyContext(
        name="client",
        hv_service=hv_service,
        comp_service=MBService(address=_SERVER_IP, port=_CLIENT_PORT),
    )
    server.MB_RegisterService(
        my_service=server.my_service,
        hv_service=hv_service,
        service_type="test",
        service_name=_SERVICE_NAME,
    )
    yield server, client
    server.MB_Finalize()
    client.MB_EasyFinalize()


@pytest.fixture
def subscriber_and_publisher(hv_address, hv_port):
    hv_service = MBService(address=hv_address, port=hv_port)
    subscriber = MBEasyContext(
        name="subscriber",
        hv_service=hv_service,
        comp_service=MBService(address=_SERVER_IP, port=_SERVER_PORT),
    )
    publisher = MBEasyContext(
        name="publisher",
        hv_service=hv_service,
        comp_service=MBService(address=_SERVER_IP, port=_CLIENT_PORT),
    )
    yield subscriber, publisher
    subscriber.MB_EasyFinalize()
    publisher.MB_EasyFinalize()


def test_easy_send_message(server_and_client):
    server, client = server_and_client
    sent_data = b"hello easy"

    def server_thread():
        msg = server.MB_GetMessage()
        assert_that(msg.data).is_equal_to(sent_data)
        msg.token = TK_SYS_ACK
        server.MB_ReplyMessage(message=msg, data=b"ok")

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

    reply = client.MB_EasySendMessage(
        dest_name=_SERVICE_NAME,
        token=msgbus.TK_CMP_GETSERVICES,
        data=sent_data,
    )
    assert_that(reply.token).is_equal_to(TK_SYS_ACK)
    assert_that(reply.data).is_equal_to(b"ok")

    t.join()


def test_easy_send_full_message(server_and_client):
    server, client = server_and_client
    sent_data = b"full message data"
    sent_headers = b"some-headers"

    def server_thread():
        msg = server.MB_GetMessage()
        assert_that(msg.data).is_equal_to(sent_data)
        assert_that(msg.headers).is_equal_to(sent_headers)
        assert_that(msg.ttl).is_equal_to(42)
        msg.token = TK_SYS_ACK
        server.MB_ReplyMessage(message=msg, data=b"full-ok")

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

    reply = client.MB_EasySendFullMessage(
        dest_name=_SERVICE_NAME,
        token=msgbus.TK_CMP_GETSERVICES,
        data=sent_data,
        headers=sent_headers,
        ttl=42,
    )
    assert_that(reply.token).is_equal_to(TK_SYS_ACK)
    assert_that(reply.data).is_equal_to(b"full-ok")

    t.join()


def test_easy_send_uses_cache_on_second_call(server_and_client):
    server, client = server_and_client

    def server_thread():
        for _ in range(2):
            msg = server.MB_GetMessage()
            msg.token = TK_SYS_ACK
            server.MB_ReplyMessage(message=msg, data=b"ok")

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

    client.MB_EasySendMessage(dest_name=_SERVICE_NAME, token=msgbus.TK_CMP_GETSERVICES)
    assert_that(client.dest_cache).contains_key(_SERVICE_NAME)

    client.MB_EasySendMessage(dest_name=_SERVICE_NAME, token=msgbus.TK_CMP_GETSERVICES)
    assert_that(client.dest_cache).contains_key(_SERVICE_NAME)

    t.join()


def test_easy_send_clears_cache_on_failure(hv_address, hv_port):
    server2_port = 14594
    server = MBContext(
        my_service=MBService(address=_SERVER_IP, port=_SERVER_PORT),
        name="server",
    )
    client = MBEasyContext(
        name="client",
        hv_service=MBService(address=hv_address, port=hv_port),
        comp_service=MBService(address=_SERVER_IP, port=_CLIENT_PORT),
    )

    hv_service = MBService(address=hv_address, port=hv_port)

    server.MB_RegisterService(
        my_service=server.my_service,
        hv_service=hv_service,
        service_type="test",
        service_name=_SERVICE_NAME,
    )

    def server_thread():
        msg = server.MB_GetMessage()
        msg.token = TK_SYS_ACK
        server.MB_ReplyMessage(message=msg, data=b"ok")

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

    client.MB_EasySendMessage(dest_name=_SERVICE_NAME, token=msgbus.TK_CMP_GETSERVICES)
    assert_that(client.dest_cache).contains_key(_SERVICE_NAME)

    t.join()
    server.MB_Finalize()

    server2 = MBContext(
        my_service=MBService(address=_SERVER_IP, port=server2_port),
        name="server2",
    )
    server2.MB_RegisterService(
        my_service=server2.my_service,
        hv_service=hv_service,
        service_type="test",
        service_name=_SERVICE_NAME,
    )

    try:
        client.MB_EasySendMessage(dest_name=_SERVICE_NAME, token=msgbus.TK_CMP_GETSERVICES)
        fail("Must raise MBException")
    except MBException:
        assert_that(client.dest_cache).does_not_contain_key(_SERVICE_NAME)

    def server2_thread():
        msg = server2.MB_GetMessage()
        msg.token = TK_SYS_ACK
        server2.MB_ReplyMessage(message=msg, data=b"ok2")

    t2 = Thread(target=server2_thread)
    t2.start()

    reply = client.MB_EasySendMessage(dest_name=_SERVICE_NAME, token=msgbus.TK_CMP_GETSERVICES)
    assert_that(reply.token).is_equal_to(TK_SYS_ACK)

    t2.join()
    server2.MB_Finalize()
    client.MB_EasyFinalize()


def test_easy_get_and_reply_message(hv_address, hv_port):
    server = MBEasyContext(
        name="server",
        hv_service=MBService(address=hv_address, port=hv_port),
        comp_service=MBService(address=_SERVER_IP, port=_SERVER_PORT),
    )
    client = MBContext(
        my_service=MBService(address=_SERVER_IP, port=_CLIENT_PORT),
        name="client",
    )

    sent_data = b"get and reply"

    def server_thread():
        msg = server.MB_EasyGetMessage()
        assert_that(msg.data).is_equal_to(sent_data)
        msg.token = TK_SYS_ACK
        server.MB_EasyReplyMessage(message=msg)

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

    reply = client.MB_SendMessage(
        destination=MBService(address=_SERVER_IP, port=_SERVER_PORT),
        token=msgbus.TK_CMP_GETSERVICES,
        data=sent_data,
    )
    assert_that(reply.token).is_equal_to(TK_SYS_ACK)

    t.join()
    server.MB_EasyFinalize()
    client.MB_Finalize()


def test_easy_register_service(hv_address, hv_port):
    hv_service = MBService(address=hv_address, port=hv_port)

    comp = MBEasyContext(
        name="comp",
        hv_service=hv_service,
        comp_service=MBService(address=_SERVER_IP, port=_SERVER_PORT),
    )

    locator = MBContext(
        my_service=MBService(address=_SERVER_IP, port=_CLIENT_PORT),
        name="locator",
    )

    service_name = "easy-registered-service"
    comp.MB_EasyRegisterService(service_type="test", service_name=service_name)

    located = locator.MB_LocateService(hv_service=hv_service, service_name=service_name)
    assert_that(located.address).is_equal_to(_SERVER_IP)
    assert_that(located.port).is_equal_to(_SERVER_PORT)

    comp.MB_EasyFinalize()
    locator.MB_Finalize()


def test_easy_evt_subscribe_and_send(subscriber_and_publisher):
    subscriber, publisher = subscriber_and_publisher

    subject = "EASY_EVT_SUBJECT"
    event_type = "EASY_EVT_TYPE"
    event_data = "easy event data"

    subscriber.MB_EasyEvtSubscribe(subject=subject)

    def publish_thread():
        publisher.MB_EasyEvtSend(
            subject=subject,
            type=event_type,
            xml=event_data,
        )

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

    msg = subscriber.MB_EasyGetMessage()
    event = Event.build_from_message(
        message=Message(
            token=msg.token,
            data_type=msg.format,
            data=msg.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())

    t.join()


def test_easy_evt_unsubscribe(subscriber_and_publisher):
    subscriber, publisher = subscriber_and_publisher

    subject = "EASY_UNSUB_SUBJECT"

    subscriber.MB_EasyEvtSubscribe(subject=subject)

    def publish_thread():
        publisher.MB_EasyEvtSend(subject=subject, type="T", xml="data1")

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

    subscriber.MB_EasyGetMessage()
    t.join()

    subscriber.MB_EasyEvtUnsubscribe(subject=subject)


def test_easy_wait_start(hv_address, hv_port):
    hv_simulator = MBContext(
        my_service=MBService(address=_SERVER_IP, port=_HV_SIM_PORT),
        name="hv-sim",
    )
    comp = MBEasyContext(
        name="comp",
        hv_service=MBService(address=_SERVER_IP, port=_HV_SIM_PORT),
        comp_service=MBService(address=_SERVER_IP, port=_COMP_PORT),
    )

    exported_services = "svc1|svc2"
    required_services = "svc3|svc4"

    def hv_thread():
        comp_service = MBService(address=_SERVER_IP, port=_COMP_PORT)

        reply = hv_simulator.MB_SendFullMessage(destination=comp_service, token=TK_CMP_GETDEPENDS)
        assert_that(reply.token).is_equal_to(TK_SYS_ACK)
        assert_that(reply.data.decode()).is_equal_to(required_services)

        reply = hv_simulator.MB_SendFullMessage(destination=comp_service, token=TK_CMP_GETSERVICES)
        assert_that(reply.token).is_equal_to(TK_SYS_ACK)
        assert_that(reply.data.decode()).is_equal_to(exported_services)

        reply = hv_simulator.MB_SendFullMessage(destination=comp_service, token=TK_CMP_START)
        assert_that(reply.token).is_equal_to(TK_SYS_ACK)

        hv_simulator.MB_Finalize()

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

    ret = comp.MB_EasyWaitStart(
        exported_services=exported_services,
        required_services=required_services,
    )
    assert_that(ret).is_equal_to(SE_SUCCESS)

    t.join()
    comp.MB_EasyFinalize()
