import pytest
from napari_plugin_engine import napari_hook_implementation
from qtpy.QtWidgets import QWidget

import napari
from napari import Viewer, plugins
from napari.plugins import hook_specifications


class Widg1(QWidget):
    pass


class Widg2(QWidget):
    def __init__(self, napari_viewer):
        self.viewer = napari_viewer
        super().__init__()


class Widg3(QWidget):
    def __init__(self, v: Viewer):
        self.viewer = v
        super().__init__()


def magicfunc(viewer: 'napari.Viewer'):
    return viewer


dwidget_args = {
    'single_class': Widg1,
    'class_tuple': (Widg1, {'area': 'right'}),
    'tuple_list': [(Widg1, {'area': 'right'}), (Widg2, {})],
    'tuple_list2': [(Widg1, {'area': 'right'}), Widg2],
    'bad_class': 1,
    'bad_tuple1': (Widg1, 1),
    'bad_double_tuple': ((Widg1, {}), (Widg2, {})),
}


# test_plugin_manager and add_implementation fixtures are
#     provided by napari_plugin_engine._testsupport
# monkeypatch, request, recwarn fixtures are from pytest
@pytest.mark.parametrize('arg', dwidget_args.values(), ids=dwidget_args.keys())
def test_dock_widget_registration(
    arg, test_plugin_manager, monkeypatch, request, recwarn
):
    """Test that dock widgets get validated and registerd correctly."""
    test_plugin_manager.project_name = 'napari'
    test_plugin_manager.add_hookspecs(hook_specifications)
    hook = test_plugin_manager.hook.napari_experimental_provide_dock_widget

    with monkeypatch.context() as m:
        registered = {}
        m.setattr(plugins, "dock_widgets", registered)

        class Plugin:
            @napari_hook_implementation
            def napari_experimental_provide_dock_widget():
                return arg

        test_plugin_manager.register(Plugin)

        hook.call_historic(
            result_callback=plugins.register_dock_widget, with_impl=True
        )
        if '[bad_' in request.node.name:
            assert len(recwarn) == 1
            assert not registered
        else:
            assert len(recwarn) == 0
            assert registered['Plugin']['Widg1'][0] == Widg1
            if 'tuple_list' in request.node.name:
                assert registered['Plugin']['Widg2'][0] == Widg2


@pytest.fixture
def test_plugin_widgets(monkeypatch):
    """A smattering of example registered dock widgets and function widgets."""
    with monkeypatch.context() as m:
        dock_widgets = {
            "TestP1": {"Widg1": (Widg1, {}), "Widg2": (Widg2, {})},
            "TestP2": {"Widg3": (Widg3, {})},
        }
        m.setattr(plugins, "dock_widgets", dock_widgets)

        function_widgets = {'TestP3': {'magic': magicfunc}}
        m.setattr(plugins, "function_widgets", function_widgets)
        yield


def test_plugin_widgets_menus(test_plugin_widgets, make_napari_viewer):
    """Test the plugin widgets get added to the window menu correctly."""
    viewer = make_napari_viewer()
    actions = viewer.window._plugin_dock_widget_menu.actions()
    assert len(actions) == 3
    expected_text = ['TestP1', 'TestP2: Widg3', 'TestP3: magic']
    assert [a.text() for a in actions] == expected_text

    # the first item in the menu is a submenu (for "Test plugin1")
    assert actions[0].menu()
    subnames = ['Widg1', 'Widg2']
    assert [a.text() for a in actions[0].menu().actions()] == subnames

    # the other items in the menu are not submenus
    assert not actions[1].menu()
    assert not actions[2].menu()


def test_making_plugin_dock_widgets(test_plugin_widgets, make_napari_viewer):
    """Test that we can create dock widgets, and they get the viewer."""
    viewer = make_napari_viewer()
    actions = viewer.window._plugin_dock_widget_menu.actions()

    # trigger the 'TestP2: Widg3' action
    actions[1].trigger()
    # make sure that a dock widget was created
    assert 'TestP2: Widg3' in viewer.window._dock_widgets
    dw = viewer.window._dock_widgets['TestP2: Widg3']
    assert isinstance(dw.widget(), Widg3)
    # This widget uses the parameter annotation method to receive a viewer
    assert isinstance(dw.widget().viewer, napari.Viewer)
    # Add twice is ok, only does a show
    actions[1].trigger()

    # trigger the 'TestP1 > Widg2' action (it's in a submenu)
    action = actions[0].menu().actions()[1]
    assert action.text() == 'Widg2'
    action.trigger()
    # make sure that a dock widget was created
    assert 'TestP1: Widg2' in viewer.window._dock_widgets
    dw = viewer.window._dock_widgets['TestP1: Widg2']
    assert isinstance(dw.widget(), Widg2)
    # This widget uses parameter *name* "napari_viewer" to get a viewer
    assert isinstance(dw.widget().viewer, napari.Viewer)
    # Add twice is ok, only does a show
    action.trigger()


def test_making_function_dock_widgets(test_plugin_widgets, make_napari_viewer):
    """Test that we can create magicgui widgets, and they get the viewer."""
    import magicgui

    viewer = make_napari_viewer()
    actions = viewer.window._plugin_dock_widget_menu.actions()

    # trigger the 'TestP3: magic' action
    actions[2].trigger()
    # make sure that a dock widget was created
    assert 'TestP3: magic' in viewer.window._dock_widgets
    dw = viewer.window._dock_widgets['TestP3: magic']
    # make sure that it contains a magicgui widget
    magic_widget = dw.widget()._magic_widget
    FGui = getattr(magicgui.widgets, 'FunctionGui', None)
    if FGui is None:
        # pre magicgui 0.2.6
        FGui = magicgui.FunctionGui
    assert isinstance(magic_widget, FGui)
    # This magicgui widget uses the parameter annotation to receive a viewer
    assert isinstance(magic_widget.viewer.value, napari.Viewer)
    # The function just returns the viewer... make sure we can call it
    assert isinstance(magic_widget(), napari.Viewer)
    # Add twice is ok, only does a show
    actions[2].trigger()


def test_clear_all_plugin_widgets(test_plugin_widgets, make_napari_viewer):
    """Test the the 'Remove Dock Widgets' menu item clears added widgets."""
    viewer = make_napari_viewer()
    actions = viewer.window._plugin_dock_widget_menu.actions()
    actions[1].trigger()
    actions[0].menu().actions()[1].trigger()
    assert len(viewer.window._dock_widgets) == 2
    clear_action = next(
        a
        for a in viewer.window.window_menu.actions()
        if a.text().startswith("Remove Dock Widgets")
    )
    clear_action.trigger()
    assert len(viewer.window._dock_widgets) == 0
