"""ConfigReloadMixin 配置变更事件处理器解析的回归测试（Issue #6329）。

ConfigReloadMixin 动态生成的事件处理器，必须能被事件总线解析回实例方法，
不能因 __name__ 与类上方法名不一致而被静默跳过。
"""

import inspect

import pytest

from app.runtime.events import Event, EventHandlerBinding, eventmanager
from app.runtime.reload import ConfigReloadMixin
from app.schemas import ConfigChangeEventData
from app.schemas.types import EventType


class _ReloadRecorder(ConfigReloadMixin):
    """测试用同步重载子类，记录重载调用次数。"""

    CONFIG_WATCH = {"TEST_RELOAD_KEY"}

    def __init__(self):
        self.reload_count = 0

    def on_config_changed(self):
        self.reload_count += 1


class _AsyncReloadRecorder(ConfigReloadMixin):
    """测试用异步重载子类，记录重载调用次数。"""

    CONFIG_WATCH = {"TEST_RELOAD_KEY"}

    def __init__(self):
        self.reload_count = 0

    async def on_config_changed(self):
        self.reload_count += 1


@pytest.fixture(params=[_ReloadRecorder, _AsyncReloadRecorder])
def recorder(request, monkeypatch):
    """将生成的处理器绑定到测试实例，并在测试后移除全局监听。"""
    recorder_cls = request.param
    instance = recorder_cls()

    def resolver(owner_class):
        if owner_class is recorder_cls:
            return EventHandlerBinding(instance=instance, owner_name=owner_class.__name__)
        return None

    # 隔离真实解析器，仅绑定测试实例
    monkeypatch.setattr(
        eventmanager,
        "_EventManager__handler_instance_resolvers",
        {"test_reload": resolver},
    )
    yield instance
    # 移除类定义时注册的全局监听，避免影响其余测试
    eventmanager.remove_event_listener(
        EventType.ConfigChanged, recorder_cls.handle_config_changed
    )


def _build_event(keys):
    """构造携带指定配置键的 ConfigChanged 事件。"""
    return Event(EventType.ConfigChanged, ConfigChangeEventData(key=set(keys)))


async def _dispatch(instance, event):
    """按处理器类型走事件总线的同步或异步调用路径。"""
    handler = instance.__class__.handle_config_changed
    if inspect.iscoroutinefunction(handler):
        await eventmanager._EventManager__invoke_handler_by_type_async(handler, event)
    else:
        eventmanager._EventManager__invoke_handler_by_type_sync(handler, event)


def test_generated_handler_exposes_method_name(recorder):
    """生成的处理器 __name__ 必须与类上的方法名一致，保证事件总线能解析到实例方法。"""
    handler = recorder.__class__.handle_config_changed
    assert handler.__name__ == "handle_config_changed"
    assert handler.__qualname__ == f"{recorder.__class__.__name__}.handle_config_changed"


@pytest.mark.asyncio
async def test_config_changed_event_triggers_reload(recorder):
    """命中 CONFIG_WATCH 的配置变更事件必须触发子类重载逻辑。"""
    await _dispatch(recorder, _build_event({"TEST_RELOAD_KEY"}))
    assert recorder.reload_count == 1


@pytest.mark.asyncio
async def test_unrelated_config_key_skips_reload(recorder):
    """未监听的配置键变更不得触发重载。"""
    await _dispatch(recorder, _build_event({"OTHER_KEY"}))
    assert recorder.reload_count == 0


@pytest.mark.asyncio
async def test_resolve_falls_back_to_qualname_when_name_mismatched(monkeypatch):
    """处理器 __name__ 与类上方法名不一致时，事件总线应回退 __qualname__ 末段解析。"""
    instance = _ReloadRecorder()

    def wrapper(event):  # pylint: disable=unused-argument
        """模拟只同步了 __qualname__ 的动态处理器。"""
        instance.on_config_changed()

    wrapper.__module__ = _ReloadRecorder.__module__
    wrapper.__qualname__ = f"{_ReloadRecorder.__name__}.handle_config_changed"

    def resolver(owner_class):
        if owner_class is _ReloadRecorder:
            return EventHandlerBinding(instance=instance, owner_name=owner_class.__name__)
        return None

    monkeypatch.setattr(
        eventmanager,
        "_EventManager__handler_instance_resolvers",
        {"test_reload": resolver},
    )

    await eventmanager._EventManager__invoke_handler_by_type_async(
        wrapper, _build_event({"TEST_RELOAD_KEY"})
    )

    assert wrapper.__name__ == "wrapper"
    assert instance.reload_count == 1


def test_externally_managed_reload_class_does_not_register_listener(monkeypatch):
    """外部统一管理配置生命周期时，Mixin 保留重载能力但不重复绑定事件。"""
    registrations = []
    monkeypatch.setattr(
        eventmanager,
        "add_event_listener",
        lambda *args, **kwargs: registrations.append((args, kwargs)),
    )

    class _ExternallyManagedReloadRecorder(ConfigReloadMixin):
        CONFIG_RELOAD_MANAGED_EXTERNALLY = True
        CONFIG_WATCH = {"TEST_RELOAD_KEY"}

        def on_config_changed(self):
            pass

    assert registrations == []
    assert "handle_config_changed" not in _ExternallyManagedReloadRecorder.__dict__
