import threading
from typing import (
    Any,
    Optional,
)

import pytest
from edpconfigprovidermessagehandler import MultiTenantConfigProviderMessageHandlerBuilder
from messagebus import (
    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_builds_handler_with_global_and_tenant_config():
    builder = MyMultiTenantBuilder()

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

    assert handler.config is builder.built_config
    assert handler.tenant_config == {"tenant": "tenant_a"}


def test_caches_tenant_config_across_calls():
    builder = MyMultiTenantBuilder()

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

    assert builder.tenant_config_factory.build_calls["tenant_a"] == 1


def test_separates_cache_per_tenant():
    builder = MyMultiTenantBuilder()

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

    assert h1.tenant_config == {"tenant": "tenant_a"}
    assert h2.tenant_config == {"tenant": "tenant_b"}
    assert builder.tenant_config_factory.build_calls == {"tenant_a": 1, "tenant_b": 1}


def test_global_config_is_memoized():
    builder = MyMultiTenantBuilder()

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

    assert builder.config_transformer.build_config_call_count == 1


def test_does_not_cache_when_build_tenant_returns_none():
    builder = MyMultiTenantBuilder(tenant_config_factory=StubTenantConfigFactory(return_value=None))

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

    assert "tenant_a" not in builder.tenant_dict
    assert builder.tenant_config_factory.build_calls["tenant_a"] == 1


def test_exception_handler_intercepts_missing_tenant_config():
    fallback = object()
    builder = MyMultiTenantBuilder(
        tenant_config_factory=StubTenantConfigFactory(return_value=None),
        exception_handler=lambda ex: fallback,
    )

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

    assert result is fallback


def test_raises_when_tenant_mandatory_and_no_tenant_header():
    builder = MyMultiTenantBuilder()

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

    assert builder.build_calls == 0


def test_exception_handler_intercepts_missing_tenant_header():
    fallback = object()
    builder = MyMultiTenantBuilder(exception_handler=lambda ex: fallback)

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

    assert result is fallback


def test_concurrent_resolution_builds_tenant_once():
    factory = StubTenantConfigFactory(slow=True)
    builder = MyMultiTenantBuilder(tenant_config_factory=factory)

    errors = []

    def worker():
        try:
            builder.build_message_handler(
                message=Message(token=1, headers={"tenant": "tenant_a"}),
                event=None,
            )
        except Exception as ex:
            errors.append(ex)

    threads = [threading.Thread(target=worker) for _ in range(8)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()

    assert not errors
    assert factory.build_calls["tenant_a"] == 1


# --- 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 StubTenantConfigFactory:
    def __init__(self, return_value: Any = "__default__", slow: bool = False):
        self._return_value = return_value
        self._slow = slow
        self.build_calls = {}

    def build_config(self, tenant_name: str):
        self.build_calls[tenant_name] = self.build_calls.get(tenant_name, 0) + 1
        if self._slow:
            import time
            time.sleep(0.05)
        if self._return_value == "__default__":
            return {"tenant": tenant_name}
        return self._return_value


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

    def handle_event(self, message_bus, event):
        return

    def handle_message(self, message_bus, message):
        return


class MyMultiTenantBuilder(MultiTenantConfigProviderMessageHandlerBuilder):
    def __init__(
        self,
        message_bus: Optional[MessageBus] = None,
        tenant_mandatory: bool = True,
        tenant_config_factory: Optional[StubTenantConfigFactory] = None,
        exception_handler=None,
    ):
        self.built_config = object()
        super().__init__(
            config_transformer=StubConfigMapper(self.built_config),
            tenant_config_factory=tenant_config_factory or StubTenantConfigFactory(),
            message_bus=message_bus,
            tenant_mandatory=tenant_mandatory,
            exception_handler=exception_handler,
        )
        self.build_calls = 0

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

    def build_singletons(self):
        pass

    def destroy_message_handler(self, message_handler):
        pass

    def destroy_singletons(self):
        pass
