"""Tests for MessageBuilder, Message, and builder chain behaviour.

These tests do NOT require a running Kafka broker — they test the
builder's state management, serialisation, and debug output.
"""

import pytest
from broker_core import MessageBuilder, Message
from broker_core.message.routing import TopicPattern, PartitionTarget


# ---------------------------------------------------------------------------
# Dummy client for unit-testing the builder without Kafka
# ---------------------------------------------------------------------------


class _DummyClient:
    """Minimal client stub that satisfies the MessageBuilder contract."""

    async def _publish(self, message):
        return None  # not called in these tests

    async def _subscribe(self, message, builder):
        return None  # not called in these tests


@pytest.fixture
def builder():
    """Return a MessageBuilder attached to a dummy client."""
    return MessageBuilder(_DummyClient(), "test-topic")


# ---------------------------------------------------------------------------
# Routing primitives
# ---------------------------------------------------------------------------


class TestTopicPattern:
    def test_default_exact(self):
        tp = TopicPattern("orders")
        assert tp.exact is True
        assert str(tp) == "orders"

    def test_wildcard(self):
        tp = TopicPattern("orders.*", exact=False)
        assert str(tp) == "orders.*"
        assert tp.exact is False


class TestPartitionTarget:
    def test_any_is_none(self):
        pt = PartitionTarget.ANY()
        assert pt.partition is None

    def test_specific(self):
        pt = PartitionTarget.specific(3)
        assert pt.partition == 3

    def test_specific_negative_raises(self):
        with pytest.raises(ValueError):
            PartitionTarget.specific(-1)


# ---------------------------------------------------------------------------
# Message dataclass
# ---------------------------------------------------------------------------


class TestMessage:
    def test_defaults(self):
        msg = Message(topic="orders")
        assert msg.topic == "orders"
        assert msg.key is None
        assert msg.payload is None
        assert msg.headers == {}
        assert msg.partition is None

    def test_to_dict(self):
        msg = Message(
            topic="orders",
            key="k1",
            payload={"x": 1},
            headers={"h": "v"},
            partition=2,
        )
        d = msg.to_dict()
        assert d["topic"] == "orders"
        assert d["key"] == "k1"
        assert d["payload"] == {"x": 1}
        assert d["headers"] == {"h": "v"}
        assert d["partition"] == 2

    def test_debug(self):
        msg = Message(topic="t", key="k", payload={"a": 1})
        output = msg.debug()
        assert "t" in output
        assert "k" in output
        assert "a" in output


# ---------------------------------------------------------------------------
# MessageBuilder — content chain
# ---------------------------------------------------------------------------


class TestMessageBuilderContent:
    def test_key(self, builder):
        builder.key("my-key")
        assert builder._key == "my-key"

    def test_payload(self, builder):
        builder.payload({"status": "ok"})
        assert builder._payload == {"status": "ok"}

    def test_headers_merge(self, builder):
        builder.headers({"a": "1"})
        builder.headers({"b": "2"})
        assert builder._headers == {"a": "1", "b": "2"}

    def test_header_single(self, builder):
        builder.header("x-trace", "abc123")
        assert builder._headers == {"x-trace": "abc123"}

    def test_partition(self, builder):
        builder.partition(5)
        assert builder._partition == 5

    def test_build_message(self, builder):
        msg = builder.key("k").payload({"x": 1}).headers({"h": "v"})._build_message()
        assert msg.topic == "test-topic"
        assert msg.key == "k"
        assert msg.payload == {"x": 1}
        assert msg.headers == {"h": "v"}

    def test_to_dict(self, builder):
        d = builder.key("k").payload({"a": 1}).to_dict()
        assert d["key"] == "k"
        assert d["payload"] == {"a": 1}

    def test_debug(self, builder):
        output = builder.key("k").payload({"x": 1}).debug()
        assert "test-topic" in output
        assert "k" in output

    def test_chain_fluent(self, builder):
        """Verify the full chain compiles without error."""
        result = (
            builder
            .key("order-123")
            .payload({"id": 123, "status": "created", "amount": 99.95})
            .headers({"source": "web", "region": "us-east"})
            .to_dict()
        )
        assert result["topic"] == "test-topic"
        assert result["key"] == "order-123"
        assert result["payload"]["status"] == "created"
        assert result["headers"]["source"] == "web"


# ---------------------------------------------------------------------------
# MessageBuilder — consumer config chain
# ---------------------------------------------------------------------------


class TestMessageBuilderConsumerConfig:
    def test_group_id(self, builder):
        builder.group_id("my-group")
        assert builder._consumer_group_id == "my-group"

    def test_client_id(self, builder):
        builder.client_id("consumer-1")
        assert builder._consumer_client_id == "consumer-1"

    def test_auto_offset_reset_valid(self, builder):
        builder.auto_offset_reset("earliest")
        assert builder._auto_offset_reset == "earliest"

    def test_auto_offset_reset_invalid(self, builder):
        with pytest.raises(ValueError, match="auto_offset_reset"):
            builder.auto_offset_reset("invalid")

    def test_from_beginning(self, builder):
        builder.from_beginning()
        assert builder._auto_offset_reset == "earliest"

    def test_enable_auto_commit(self, builder):
        builder.enable_auto_commit(False)
        assert builder._enable_auto_commit is False

    def test_max_poll_records(self, builder):
        builder.max_poll_records(50)
        assert builder._max_poll_records == 50

    def test_session_timeout_ms(self, builder):
        builder.session_timeout_ms(30000)
        assert builder._session_timeout_ms == 30000


# ---------------------------------------------------------------------------
# MessageBuilder — reset behaviour
# ---------------------------------------------------------------------------


class TestMessageBuilderReset:
    def test_reset_clears_content(self, builder):
        """After _reset(), content fields are cleared but consumer config persists."""
        builder.key("k").payload({"x": 1}).group_id("g")
        builder._reset()
        assert builder._key is None
        assert builder._payload is None
        assert builder._headers == {}
        assert builder._partition is None
        # Consumer config survives reset (sticky)
        assert builder._consumer_group_id == "g"

    def test_reuse_after_reset(self, builder):
        """Builder can be used again after a reset."""
        msg1 = builder.key("k1")._build_message()
        assert msg1.key == "k1"

        builder._reset()
        msg2 = builder.key("k2")._build_message()
        assert msg2.key == "k2"
        assert msg2.topic == "test-topic"
