package de.renew.draw.ui.impl.services;

import java.awt.Dialog;
import java.awt.Frame;
import java.util.stream.Stream;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.MockedStatic;

import CH.ifa.draw.DrawPlugin;
import CH.ifa.draw.application.MenuManager;
import CH.ifa.draw.application.WindowsMenu;
import de.renew.draw.ui.impl.menus.internal.MenuHelper;
import de.renew.draw.ui.ontology.MenuSectionPosition;
import de.renew.draw.ui.ontology.MenuSeparatorFactory;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class MenuServiceImplTest {

    private static final String DUMMY_STRING = "Dummy-String";
    private static final int DUMMY_INT = 123;
    private static final Runnable DUMMY_RUNNABLE = () -> {
    };

    private MockedStatic<MenuManager> _mockedStaticMenuManager;
    private MockedStatic<MenuHelper> _mockedMenuHelper;
    private WindowsMenu _mockedWindowsMenu;
    private MenuManager _mockedMenuManager;
    private MenuServiceImpl _service;

    @BeforeEach
    void setUp() {
        _service = new MenuServiceImpl();
        _mockedStaticMenuManager = mockStatic(MenuManager.class);
        _mockedMenuHelper = mockStatic(MenuHelper.class);
        _mockedMenuManager = mock(MenuManager.class);
        _mockedStaticMenuManager.when(MenuManager::getInstance).thenReturn(_mockedMenuManager);
        _mockedWindowsMenu = mock(WindowsMenu.class);
        when(_mockedMenuManager.getWindowsMenu()).thenReturn(_mockedWindowsMenu);
    }

    @AfterEach
    void tearDown() {
        _mockedStaticMenuManager.close();
        _mockedMenuHelper.close();
        _service = null;
        _mockedStaticMenuManager = null;
        _mockedMenuManager = null;
        _mockedMenuHelper = null;
    }

    @Test
    void testRegisterMenuWithParentAndMenu() {
        // given
        JMenuItem menuItem = mock(JMenuItem.class);

        // when
        _service.registerMenu(DUMMY_STRING, menuItem);

        // then
        verify(_mockedMenuManager).registerMenu(DUMMY_STRING, menuItem);
    }

    @Test
    void testRegisterMenuWithParentAndMenuAndId() {
        // given
        String parent = DUMMY_STRING + "1";
        String id = DUMMY_STRING + "2";
        JMenuItem menuItem = mock(JMenuItem.class);

        // when
        _service.registerMenu(parent, menuItem, id);

        // then
        verify(_mockedMenuManager).registerMenu(parent, menuItem, id);
    }

    @ParameterizedTest
    @MethodSource("getPositionWhereCombinations")
    void testRegisterMenuWithParentAndMenuAndSectionAndPosition(
        MenuSectionPosition position, int expectedWhere)
    {
        // given
        JMenuItem menuItem = mock(JMenuItem.class);

        // when
        _service.registerMenu(DUMMY_STRING, menuItem, DUMMY_INT, position);

        // then
        verify(_mockedMenuManager).registerMenu(DUMMY_STRING, menuItem, DUMMY_INT, expectedWhere);
    }

    @Test
    void testUnregisterMenuWithMenu() {
        // given
        JMenuItem menuItem = mock(JMenuItem.class);

        // when
        _service.unregisterMenu(menuItem);

        // then
        verify(_mockedMenuManager).unregisterMenu(menuItem);
    }

    @Test
    void testUnregisterMenuWithId() {
        // when
        _service.unregisterMenu(DUMMY_STRING);

        // then
        verify(_mockedMenuManager).unregisterMenu(DUMMY_STRING);
    }

    @Test
    void testCreateTopLevelMenu() {
        // given
        JMenu mockedMenu = mock(JMenu.class);
        when(_mockedMenuManager.createTopLevelMenu(any(), anyInt())).thenReturn(mockedMenu);

        // when
        JMenu actualMenu = _service.createTopLevelMenu(DUMMY_STRING, DUMMY_INT);

        // then
        assertThat(mockedMenu).isEqualTo(actualMenu);
        verify(_mockedMenuManager).createTopLevelMenu(DUMMY_STRING, DUMMY_INT);
    }

    @Test
    void testCreateMenuItemWithoutShortcut() {
        // given
        JMenuItem expectedMenuItem = mock(JMenuItem.class);
        _mockedMenuHelper.when(() -> MenuHelper.createMenuItem(any(), any()))
            .thenReturn(expectedMenuItem);

        // when
        JMenuItem actualMenuItem = _service.createMenuItem(DUMMY_STRING, DUMMY_RUNNABLE);

        // then
        assertThat(actualMenuItem).isEqualTo(expectedMenuItem);
        _mockedMenuHelper.verify(() -> MenuHelper.createMenuItem(DUMMY_STRING, DUMMY_RUNNABLE));
    }

    @Test
    void testCreateMenuItemWithShortcut() {
        // given
        JMenuItem expectedMenuItem = mock(JMenuItem.class);
        _mockedMenuHelper.when(() -> MenuHelper.createMenuItem(any(), anyInt(), any()))
            .thenReturn(expectedMenuItem);

        // when
        JMenuItem actualMenuItem = _service.createMenuItem(DUMMY_STRING, DUMMY_INT, DUMMY_RUNNABLE);

        // then
        assertThat(actualMenuItem).isEqualTo(expectedMenuItem);
        _mockedMenuHelper
            .verify(() -> MenuHelper.createMenuItem(DUMMY_STRING, DUMMY_INT, DUMMY_RUNNABLE));
    }

    private static Stream<Arguments> getPositionWhereCombinations() {
        return Stream.of(
            Arguments.of(MenuSectionPosition.BEGINNING, 0),
            Arguments.of(MenuSectionPosition.END, 1), Arguments.of(MenuSectionPosition.MIDDLE, 2));
    }

    @Test
    void testCreateMenuSeparatorFactory() {
        // when
        MenuSeparatorFactory separatorFactory = _service.createMenuSeparatorFactory(DUMMY_STRING);

        // then
        assertThat(separatorFactory).isInstanceOf(MenuManager.SeparatorFactory.class)
            .extracting("id").isEqualTo(DUMMY_STRING);
    }

    @Test
    void testCreateMenuSeparator() {
        // given
        _mockedStaticMenuManager.when(() -> MenuManager.createSeparator(DUMMY_STRING))
            .thenReturn(new JMenuItem(DUMMY_STRING));

        // when
        JMenuItem actualSeparator = _service.createMenuSeparator(DUMMY_STRING);

        // then
        assertThat(actualSeparator).isNotNull().extracting(JMenuItem::getLabel)
            .isEqualTo(DUMMY_STRING);
    }

    @Test
    void testGetMenuNamePluginsMenu() {
        // given
        String expectedString = DrawPlugin.PLUGINS_MENU;

        // when
        String actualString = _service.getMenuNamePluginsMenu();

        // then
        assertThat(actualString).isEqualTo(expectedString);
    }

    @Test
    void testGetMenuNamePlatformsMenu() {
        // given
        String expectedString = DrawPlugin.PLATFORMS_MENU;

        // when
        String actualString = _service.getMenuNamePlatformsMenu();

        // then
        assertThat(actualString).isEqualTo(expectedString);
    }

    @Test
    void testGetMenuNameFileMenu() {
        // given
        String expectedString = DrawPlugin.FILE_MENU;

        // when
        String actualString = _service.getMenuNameFileMenu();

        // then
        assertThat(actualString).isEqualTo(expectedString);
    }

    @Test
    void testGetMenuNameEditMenu() {
        // given
        String expectedString = DrawPlugin.EDIT_MENU;

        // when
        String actualString = _service.getMenuNameEditMenu();

        // then
        assertThat(actualString).isEqualTo(expectedString);
    }

    @Test
    void testGetMenuNameAttributesMenu() {
        // given
        String expectedString = DrawPlugin.ATTRIBUTES_MENU;

        // when
        String actualString = _service.getMenuNameAttributesMenu();

        // then
        assertThat(actualString).isEqualTo(expectedString);
    }

    @Test
    void testGetMenuNameLayoutMenu() {
        // given
        String expectedString = DrawPlugin.LAYOUT_MENU;

        // when
        String actualString = _service.getMenuNameLayoutMenu();

        // then
        assertThat(actualString).isEqualTo(expectedString);
    }

    @Test
    void testGetMenuNameToolsMenu() {
        // given
        String expectedString = DrawPlugin.TOOLS_MENU;

        // when
        String actualString = _service.getMenuNameToolsMenu();

        // then
        assertThat(actualString).isEqualTo(expectedString);
    }

    @Test
    void testGetMenuNamePaoseMenu() {
        // given
        String expectedString = DrawPlugin.PAOSE_MENU;

        // when
        String actualString = _service.getMenuNamePaoseMenu();

        // then
        assertThat(actualString).isEqualTo(expectedString);
    }

    @Test
    void testGetMenuNameHelpMenu() {
        // given
        String expectedString = DrawPlugin.HELP_MENU;

        // when
        String actualString = _service.getMenuNameHelpMenu();

        // then
        assertThat(actualString).isEqualTo(expectedString);
    }

    @Test
    void testGetWindowsCategoryNameTools() {
        // given
        String expectedString = DrawPlugin.WINDOWS_CATEGORY_TOOLS;

        // when
        String actualString = _service.getWindowsCategoryNameTools();

        // then
        assertThat(actualString).isEqualTo(expectedString);
    }

    @Test
    void testRegisterWindowsMenuDialog() {
        // given
        Dialog dialog = mock(Dialog.class);

        // when
        _service.registerWindowsMenuDialog(DUMMY_STRING, dialog);

        // then
        verify(_mockedWindowsMenu).addDialog(DUMMY_STRING, dialog);
    }

    @Test
    void unregisterWindowsMenuDialog() {
        // given
        Dialog dialog = mock(Dialog.class);

        // when
        _service.unregisterWindowsMenuDialog(dialog);

        // then
        verify(_mockedWindowsMenu).removeDialog(dialog);
    }

    @Test
    void testRegisterWindowsMenuFrame() {
        // given
        Frame frame = mock(JFrame.class);

        // when
        _service.registerWindowsMenuFrame(DUMMY_STRING, frame);

        // then
        verify(_mockedWindowsMenu).addFrame(DUMMY_STRING, frame);
    }

    @Test
    void testUnregisterWindowsMenuFrame() {
        // given
        Frame frame = mock(JFrame.class);

        // when
        _service.unregisterWindowsMenuFrame(frame);

        // then
        verify(_mockedWindowsMenu).removeFrame(frame);
    }

    @Test
    void testRegisterWindowsMenuItem() {
        // given
        JMenuItem menuItem = mock(JMenuItem.class);

        // when
        _service.registerWindowsMenuItem(menuItem);

        // then
        verify(_mockedWindowsMenu).add(menuItem);

    }

    @Test
    void testUnregisterWindowsMenuItem() {
        // given
        JMenuItem menuItem = mock(JMenuItem.class);

        // when
        _service.unregisterWindowsMenuItem(menuItem);

        // then
        verify(_mockedWindowsMenu).remove(menuItem);

    }
}