"""Tests for the pluggable serializer abstraction."""

import json
from dataclasses import dataclass
from typing import Any

import pytest
from pydantic import BaseModel

from kurier.core.basekurier import BaseKurier
from kurier.core.bus_types import BusType
from kurier.core.station import Station
from kurier.hermes import Hermes
from kurier.utilities.serializers import CloudEventSerializer, JsonSerializer, Serializer

Hermes.is_enabled = False


# -- helpers ------------------------------------------------------------------


class _StubLine(BaseKurier):
    bus_type = BusType.IN_MEMORY

    def __init__(self, name: str = "stub_line", **kwargs):
        super().__init__(name, **kwargs)

    def _publish_to_bus(self, json_message: str, meta_data: dict[str, Any]):
        pass

    def _run_consumer_loop(self):
        pass

    def close(self):
        pass

    @classmethod
    def start_consumer(cls, name: str) -> bool:
        return True


class ReverseJsonSerializer(Serializer):
    """Serializer that reverses the JSON string — proves custom serializer is wired in."""

    def serialize(self, data: dict) -> str:
        return json.dumps(data)[::-1]

    def deserialize(self, raw: str | bytes) -> dict:
        if isinstance(raw, bytes):
            raw = raw.decode()
        return json.loads(raw[::-1])


class OrderCreated(BaseModel):
    order_id: int
    product: str


@dataclass
class SimpleDC:
    x: int
    y: int


# -- tests --------------------------------------------------------------------


class TestJsonSerializer:
    def test_round_trip(self):
        s = JsonSerializer()
        data = {"key": "value", "num": 42}
        assert s.deserialize(s.serialize(data)) == data

    def test_serialize_returns_str(self):
        s = JsonSerializer()
        assert isinstance(s.serialize({"a": 1}), str)


class TestCloudEventSerializer:
    def test_serialize_envelope_produces_cloudevents_shape(self):
        serializer = CloudEventSerializer(source="kurier://orders")

        from kurier.core.kurier_message import KurierMessage

        cloud_event = serializer.serialize_envelope(
            KurierMessage(
                object_type_name="OrderCreated",
                qualified_object_type_name="tests.test_serializers.OrderCreated",
                message_payload='{"order_id": 11, "product": "widget"}',
                metadata={"tenant": "acme"},
            )
        )
        parsed = json.loads(cloud_event)

        assert parsed["specversion"] == "1.0"
        assert parsed["source"] == "kurier://orders"
        assert parsed["type"] == "tests.test_serializers.OrderCreated"
        assert parsed["data"]["order_id"] == 11
        assert parsed["metroobjecttypename"] == "OrderCreated"
        assert parsed["metrometadata"]["tenant"] == "acme"


class TestCustomSerializerOnLine:
    def test_default_serializer_is_json(self):
        line = _StubLine("default_ser_line")
        assert isinstance(line.serializer, JsonSerializer)

    def test_custom_serializer_assigned(self):
        custom = ReverseJsonSerializer()
        line = _StubLine("custom_ser_line", serializer=custom)
        assert line.serializer is custom

    def test_swap_serializer_after_init(self):
        line = _StubLine("swap_ser_line")
        custom = ReverseJsonSerializer()
        line.serializer = custom
        assert line.serializer is custom

    def test_serialize_envelope_uses_custom_serializer(self):
        custom = ReverseJsonSerializer()
        line = _StubLine("envelope_ser_line", serializer=custom)
        msg = OrderCreated(order_id=1, product="widget")

        envelope = line._serialize_to_envelope(msg, {})
        payload_str = envelope.message_payload

        # The payload should be reversed JSON
        assert payload_str == json.dumps({"order_id": 1, "product": "widget"})[::-1]
        # And the custom deserializer should recover the original
        assert custom.deserialize(payload_str) == {"order_id": 1, "product": "widget"}


class TestDispatchWithCustomSerializer:
    @pytest.mark.asyncio
    async def test_dispatch_deserializes_with_line_serializer(self):
        custom = ReverseJsonSerializer()
        line = _StubLine("dispatch_custom_line", serializer=custom)

        received = []

        @Station.add_handler("dispatch_custom_line")
        def handle_order(order: OrderCreated, *ctx):
            received.append(order)

        envelope = line._serialize_to_envelope(OrderCreated(order_id=7, product="gadget"), {})
        raw = envelope.model_dump_json()

        result = await Station.dispatch(line, raw)
        assert result is True
        assert len(received) == 1
        assert received[0].order_id == 7
        assert received[0].product == "gadget"

    @pytest.mark.asyncio
    async def test_dispatch_with_default_json_serializer(self):
        line = _StubLine("dispatch_json_line")

        received = []

        @Station.add_handler("dispatch_json_line")
        def handle_order(order: OrderCreated, *ctx):
            received.append(order)

        envelope = line._serialize_to_envelope(OrderCreated(order_id=99, product="thing"), {})
        raw = envelope.model_dump_json()

        result = await Station.dispatch(line, raw)
        assert result is True
        assert received[0].order_id == 99

    @pytest.mark.asyncio
    async def test_dispatch_dataclass_with_custom_serializer(self):
        custom = ReverseJsonSerializer()
        line = _StubLine("dispatch_dc_line", serializer=custom)

        received = []

        @Station.add_handler("dispatch_dc_line")
        def handle_dc(msg: SimpleDC, *ctx):
            received.append(msg)

        envelope = line._serialize_to_envelope(SimpleDC(x=10, y=20), {})
        raw = envelope.model_dump_json()

        result = await Station.dispatch(line, raw)
        assert result is True
        assert received[0].x == 10
        assert received[0].y == 20

    @pytest.mark.asyncio
    async def test_dispatch_with_cloudevents_serializer_and_event_handler(self):
        line = _StubLine("dispatch_ce_line", serializer=CloudEventSerializer(source="kurier://orders"))

        received = []
        contexts = []

        @Station.add_event_handler("com.example.orders.created", "dispatch_ce_line")
        def handle_order(order: OrderCreated, context):
            received.append(order)
            contexts.append(context)

        raw = _publish_with_event_type(line, OrderCreated(order_id=17, product="gizmo"), "com.example.orders.created")

        result = await Station.dispatch(line, raw)
        assert result is True
        assert received[0].order_id == 17
        assert contexts[0]["event_type"] == "com.example.orders.created"
        assert contexts[0]["source"] == "kurier://orders"

    @pytest.mark.asyncio
    async def test_dispatch_with_cloudevents_serializer_keeps_type_routing(self):
        line = _StubLine("dispatch_ce_type_line", serializer=CloudEventSerializer(source="kurier://orders"))

        received = []

        @Station.add_handler("dispatch_ce_type_line")
        def handle_order(order: OrderCreated, *ctx):
            received.append(order)

        raw = _publish_with_event_type(line, OrderCreated(order_id=21, product="drill"), "com.example.orders.created")

        result = await Station.dispatch(line, raw)
        assert result is True
        assert received[0].product == "drill"


def _publish_with_event_type(line: _StubLine, message: OrderCreated, event_type: str) -> str | bytes:
    envelope = line._serialize_to_envelope(message, {"cloudevents_type": event_type})
    return line.serializer.serialize_envelope(envelope)
