import threading
from typing import (
    Any,
    Optional,
)

import pytest
from contextmessagebus import MultiContextMessageBus
from edpconfigprovidermessagehandler import ConfigProviderMessageHandlerBuilder
from messagebus import (
    Event,
    Message,
    MessageBus,
)
from messagehandler import MessageHandler


@pytest.fixture(autouse=True)
def _clear_thread_attrs():
    yield
    thread = threading.current_thread()
    for attr in ("_edp_tenant_name", "_edp_context_name"):
        if hasattr(thread, attr):
            delattr(thread, attr)


def test_reads_tenant_from_message_tenant_header():
    builder = MyBuilder()

    handler = builder.build_message_handler(
        message=Message(token=1, headers={"tenant": "tenant_a"}),
        event=None,
    )

    assert handler.config is builder.built_config
    assert getattr(threading.current_thread(), "_edp_tenant_name") == "tenant_a"
    assert getattr(threading.current_thread(), "_edp_context_name") == "tenant_a"


def test_falls_back_to_context_header_when_tenant_header_missing():
    builder = MyBuilder()

    builder.build_message_handler(
        message=Message(token=1, headers={"context": "tenant_a"}),
        event=None,
    )

    assert getattr(threading.current_thread(), "_edp_tenant_name") == "tenant_a"
    assert getattr(threading.current_thread(), "_edp_context_name") == "tenant_a"


def test_tenant_header_takes_precedence_over_context_header():
    builder = MyBuilder()

    builder.build_message_handler(
        message=Message(token=1, headers={"tenant": "from_tenant", "context": "from_context"}),
        event=None,
    )

    assert getattr(threading.current_thread(), "_edp_tenant_name") == "from_tenant"


def test_reads_tenant_from_event_headers_when_message_is_none():
    builder = MyBuilder()

    builder.build_message_handler(
        message=None,
        event=Event(subject="test", headers={"tenant": "tenant_b"}),
    )

    assert getattr(threading.current_thread(), "_edp_tenant_name") == "tenant_b"


def test_calls_set_current_context_on_multi_context_message_bus():
    multi_bus = MultiContextMessageBus(message_bus=DummyMessageBus())
    builder = MyBuilder(message_bus=multi_bus)

    builder.build_message_handler(
        message=Message(token=1, headers={"tenant": "tenant_a"}),
        event=None,
    )

    assert multi_bus.local_data.context == "tenant_a"


def test_does_not_set_context_on_plain_message_bus():
    plain_bus = DummyMessageBus()
    builder = MyBuilder(message_bus=plain_bus)

    builder.build_message_handler(
        message=Message(token=1, headers={"tenant": "tenant_a"}),
        event=None,
    )

    # Plain bus has no set_current_context state to check; just ensure no AttributeError occurred
    # and the handler was still built normally.
    assert builder.build_calls == 1


def test_passes_config_from_transformer_to_subclass():
    builder = MyBuilder()

    handler = builder.build_message_handler(
        message=Message(token=1, headers={"tenant": "tenant_a"}),
        event=None,
    )

    assert handler.config is builder.built_config
    assert builder.config_transformer.build_config_call_count == 1


def test_raises_when_tenant_mandatory_and_no_tenant_header():
    builder = MyBuilder()

    with pytest.raises(RuntimeError, match="No tenant"):
        builder.build_message_handler(
            message=Message(token=1, headers={}),
            event=None,
        )

    assert not hasattr(threading.current_thread(), "_edp_tenant_name")
    assert not hasattr(threading.current_thread(), "_edp_context_name")
    assert builder.build_calls == 0


def test_does_not_raise_when_tenant_optional_and_no_tenant_header():
    builder = MyBuilder(tenant_mandatory=False)

    handler = builder.build_message_handler(
        message=Message(token=1, headers={}),
        event=None,
    )

    assert handler.config is builder.built_config
    assert not hasattr(threading.current_thread(), "_edp_tenant_name")
    assert not hasattr(threading.current_thread(), "_edp_context_name")


def test_optional_tenant_does_not_call_set_current_context():
    multi_bus = MultiContextMessageBus(message_bus=DummyMessageBus())
    builder = MyBuilder(message_bus=multi_bus, tenant_mandatory=False)

    builder.build_message_handler(
        message=Message(token=1, headers={}),
        event=None,
    )

    assert not hasattr(multi_bus.local_data, "context")


def test_optional_tenant_still_sets_thread_attrs_when_tenant_present():
    builder = MyBuilder(tenant_mandatory=False)

    builder.build_message_handler(
        message=Message(token=1, headers={"tenant": "tenant_a"}),
        event=None,
    )

    assert getattr(threading.current_thread(), "_edp_tenant_name") == "tenant_a"
    assert getattr(threading.current_thread(), "_edp_context_name") == "tenant_a"


# --- Test helpers ---


class DummyMessageBus(MessageBus):
    def publish_event(self, event):
        pass

    def send_message(self, component, message):
        pass

    def subscribe(self, subscriptions):
        pass

    def reply_message(self, message, reply_message):
        pass

    def reply_event(self, event, reply_message):
        pass


class StubConfigMapper:
    def __init__(self, config: Any):
        self._config = config
        self.build_config_call_count = 0

    def build_config(self):
        self.build_config_call_count += 1
        return self._config


class StubMessageHandler(MessageHandler):
    def __init__(self, config: Any):
        self.config = config

    def handle_event(self, message_bus, event):
        return

    def handle_message(self, message_bus, message):
        return


class MyBuilder(ConfigProviderMessageHandlerBuilder):
    def __init__(
        self,
        message_bus: Optional[MessageBus] = None,
        tenant_mandatory: bool = True,
    ):
        self.built_config = object()
        super().__init__(
            config_transformer=StubConfigMapper(self.built_config),
            message_bus=message_bus,
            tenant_mandatory=tenant_mandatory,
        )
        self.build_calls = 0

    def build_message_handler_with_config(self, config: Any) -> MessageHandler:
        self.build_calls += 1
        return StubMessageHandler(config=config)
