"Host implementation with almost all real but with a FakeMQTTClient"
import asyncio

import pytest

from aiospb.hosts import CommandRequest, Host
from aiospb.hosts.commands import CommandFollower, MetricsCreator
from aiospb.hosts.values import WriteRequest, WritingResolution
from aiospb.infra.encoding import JsonPayloadEncoder, ProtobufPayloadEncoder
from aiospb.infra.inmem import InMemMinMetricInfoCache
from aiospb.messages import Event, HostMessageSession, Message, MQTTMessage
from aiospb.shared import DataType, DeviceKey, HostOptions, Metric
from tests.fakes import FakeMQTTClient, ts

# import tracemalloc

# # Run your test
# tracemalloc.start(10)  # Capture up to 10 frames of stack traces

NKEY = DeviceKey("group/node")
HKEY = DeviceKey("hostname")


@pytest.fixture
def mqtt_client():
    return FakeMQTTClient()


@pytest.fixture
def host(mqtt_client):
    session = HostMessageSession(
        HKEY, mqtt_client, ProtobufPayloadEncoder(), JsonPayloadEncoder(), HostOptions()
    )
    return Host(
        HKEY,
        session,
        MetricsCreator(InMemMinMetricInfoCache()),
        CommandFollower(),
        HostOptions(),
    )


class A_Host:
    async def when_a_node_message_is_sent(self, msg: Message):
        ...

    def decode_host_message(self, msg: MQTTMessage):
        return Message.construct(msg.topic, JsonPayloadEncoder().decode(msg.payload))[0]

    async def should_start_and_finish_cleanly(self, host, mqtt_client):
        await host.establish_session()
        assert mqtt_client.connected

        await host.terminate_session()
        assert not mqtt_client.connected

        assert len(mqtt_client.published) == 2
        first = self.decode_host_message(mqtt_client.published[0])
        assert first.online
        assert first.key == HKEY

        second = self.decode_host_message(mqtt_client.published[1])
        assert not second.online

    def encode_node_message(self, msg: Message, seq: int, bd_seq: int | None):
        bd_metric = None
        if bd_seq is not None:
            bd_metric = Metric(ts(), bd_seq, DataType.Int32, name="bdSeq")

        return MQTTMessage(
            msg.get_topic(),
            ProtobufPayloadEncoder().encode(msg.get_payload(seq, bd_metric)),
        )

    async def should_report_ordered_node_messages(self, host, mqtt_client):
        messages = asyncio.Queue()

        async def observe(msg: Message):
            await messages.put(msg)

        host.add_observer("custom", observe)
        await host.establish_session()
        await asyncio.sleep(0.01)

        mqtt_client.messages.put(
            self.encode_node_message(
                Message(
                    Event.NODE_IS_BORN,
                    NKEY,
                    [Metric(ts(), 0, DataType.Int16, alias=1, name="Metric/Name")],
                    sending_ts=ts(),
                ),
                0,
                0,
            )
        )

        mqtt_client.messages.put(
            self.encode_node_message(
                Message(
                    Event.NODE_DATA_HAS_CHANGED,
                    NKEY,
                    [Metric(ts(), 2, DataType.Int16, alias=1, name="Metric/Name")],
                    sending_ts=ts(),
                ),
                2,
                None,
            )
        )

        mqtt_client.messages.put(
            self.encode_node_message(
                Message(
                    Event.NODE_DATA_HAS_CHANGED,
                    NKEY,
                    [Metric(ts(), 1, DataType.Int16, alias=1, name="Metric/Name")],
                    sending_ts=ts(),
                ),
                1,
                None,
            )
        )

        birth = await messages.get()
        assert birth.event == Event.NODE_IS_BORN
        assert birth.metrics[-1].value == 0

        data = await messages.get()
        assert data.event == Event.NODE_DATA_HAS_CHANGED
        assert data.metrics[-1].value == 1

        data = await messages.get()
        assert data.event == Event.NODE_DATA_HAS_CHANGED
        assert data.metrics[-1].value == 2

        await host.terminate_session()

    async def should_process_commands_to_node(self, host, mqtt_client):
        await host.establish_session()
        await asyncio.sleep(0.01)

        mqtt_client.messages.put(
            self.encode_node_message(
                Message(
                    Event.NODE_IS_BORN,
                    NKEY,
                    [Metric(ts(), 0, DataType.Int16, alias=1, name="Metric/Name")],
                    sending_ts=ts(),
                ),
                0,
                0,
            )
        )
        await asyncio.sleep(0.01)
        task = asyncio.create_task(
            host.execute_command(
                CommandRequest(NKEY, [WriteRequest(2, metric_name="Metric/Name")])
            )
        )
        await asyncio.sleep(0.01)

        mqtt_client.messages.put(
            self.encode_node_message(
                Message(
                    Event.NODE_DATA_HAS_CHANGED,
                    NKEY,
                    [Metric(ts(), 2, DataType.Int16, alias=1)],
                    sending_ts=ts(),
                ),
                1,
                None,
            )
        )
        await asyncio.sleep(0.01)

        assert task.done()
        response = await task
        assert response.resolutions[0] == WritingResolution.GoodWriting

        await host.terminate_session()
