import warnings

import pytest

from napari_plugin_engine import (
    HookImplementationMarker,
    HookSpecificationMarker,
    PluginManager,
)

hookspec = HookSpecificationMarker("example")
hookimpl = HookImplementationMarker("example")


def test_warn_when_deprecated_specified(recwarn):
    warning = DeprecationWarning("foo is deprecated")

    class Spec:
        @hookspec(warn_on_impl=warning)
        def foo(self):
            pass

    class Plugin:
        @hookimpl
        def foo(self):
            pass

    pm = PluginManager(hookspec.project_name)
    pm.add_hookspecs(Spec)

    with pytest.warns(DeprecationWarning) as records:
        pm.register(Plugin())
    (record,) = records
    assert record.message is warning
    assert record.filename == Plugin.foo.__code__.co_filename
    assert record.lineno == Plugin.foo.__code__.co_firstlineno


def test_plugin_getattr_raises_errors():
    """napari_plugin_engine must be able to handle plugins which raise weird exceptions
    when getattr() gets called (#11).
    """

    class DontTouchMe:
        def __getattr__(self, x):
            raise Exception("cant touch me")

    class Module:
        pass

    module = Module()
    module.x = DontTouchMe()

    pm = PluginManager(hookspec.project_name)
    # register() would raise an error
    pm.register(module, "donttouch")
    assert pm.plugins.get("donttouch") is module


def test_warning_on_call_vs_hookspec_arg_mismatch():
    """Verify that is a hook is called with less arguments then defined in the
    spec that a warning is emitted.
    """

    class Spec:
        @hookspec
        def myhook(self, arg1, arg2):
            pass

    class Plugin:
        @hookimpl
        def myhook(self, arg1):
            pass

    pm = PluginManager(hookspec.project_name)
    pm.register(Plugin())
    pm.add_hookspecs(Spec())

    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")

        # calling should trigger a warning
        pm.hook.myhook(arg1=1)

        assert len(warns) == 1
        warning = warns[-1]
        assert issubclass(warning.category, Warning)
        assert "Argument(s) ('arg2',)" in str(warning.message)


def test_repr():
    class Hook:
        @hookspec
        def myhook(arg): ...

    class Plugin:
        @hookimpl
        def myhook(arg):
            raise NotImplementedError()

    pm = PluginManager(hookspec.project_name)
    pm.add_hookspecs(Hook)

    assert (
        repr(pm.hook.myhook.spec)
        == "<HookSpecification 'myhook' args=('arg',)>"
    )

    plugin = Plugin()
    pname = pm.register(plugin)
    expected = f"<HookImplementation plugin={pname!r} spec='myhook'>"
    assert repr(pm.hook.myhook._nonwrappers[0]) == expected
