package de.renew.propertymanagementgui.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
import org.mockito.Mockito;

import static org.mockito.Mockito.mock;

/**
 * Unit test class for SideNavComponent
 *
 * @author 7kraft
 */
class SideNavComponentTest {

    // helpers

    private static Field field(
        @SuppressWarnings("SameParameterValue") Class<?> theClass, String name) throws Exception
    {
        Field field = theClass.getDeclaredField(name);
        field.setAccessible(true);
        return field;
    }

    private static Method method(
        @SuppressWarnings("SameParameterValue") Class<?> theClass, String name, Class<?>... params)
        throws Exception
    {
        Method method = theClass.getDeclaredMethod(name, params);
        method.setAccessible(true);
        return method;
    }

    private Map<String, Runnable> sampleEntries() {
        Map<String, Runnable> map = new LinkedHashMap<>();
        map.put("Plugin A", Mockito.mock(Runnable.class));
        map.put("Plugin B", Mockito.mock(Runnable.class));
        return map;
    }

    // test methods

    @Test
    public void testRender() throws Exception {
        //given
        Map<String, Runnable> entries = sampleEntries();
        SideNavComponent comp = new SideNavComponent(entries);

        JPanel itemsPanel = new JPanel();

        try (MockedConstruction<SideNavItemsComponent> mockedItems = Mockito.mockConstruction(
            SideNavItemsComponent.class,
            (mock, ctx) -> Mockito.when(mock.get()).thenReturn(itemsPanel))) {

            //when
            JComponent sideNav = comp.render();

            //then
            Assertions.assertThat(sideNav).isNotNull();

            Assertions.assertThat(sideNav.getLayout()).isInstanceOf(BoxLayout.class);
            int widthConst = (int) field(SideNavComponent.class, "SIDENAV_WIDTH").get(null);
            Assertions.assertThat(sideNav.getPreferredSize())
                .isEqualTo(new Dimension(widthConst, Integer.MAX_VALUE));
            Assertions.assertThat(sideNav.getMaximumSize())
                .isEqualTo(new Dimension(widthConst, Integer.MAX_VALUE));

            Assertions.assertThat(sideNav.getComponentCount()).isEqualTo(2);
            JComponent titleWrapper = (JComponent) sideNav.getComponent(0);
            Assertions.assertThat(titleWrapper.getLayout()).isInstanceOf(BorderLayout.class);
            Assertions.assertThat(titleWrapper.getBackground()).isEqualTo(Color.WHITE);

            Assertions.assertThat(titleWrapper.getComponentCount()).isEqualTo(1);
            Assertions.assertThat(titleWrapper.getComponent(0)).isInstanceOf(JLabel.class);
            JLabel lbl = (JLabel) titleWrapper.getComponent(0);
            Assertions.assertThat(lbl.getText())
                .isEqualTo(field(SideNavComponent.class, "TITLE").get(null));

            Assertions.assertThat(sideNav.getComponent(1)).isSameAs(itemsPanel);
            Assertions.assertThat(mockedItems.constructed().size()).isEqualTo(1);
        }
    }

    @Test
    public void testSetLayout() throws Exception {
        //given
        SideNavComponent comp = new SideNavComponent(sampleEntries());
        JComponent side = new JPanel();

        //when
        method(SideNavComponent.class, "setLayout", JComponent.class).invoke(comp, side);

        //then
        int widthConst = (int) field(SideNavComponent.class, "SIDENAV_WIDTH").get(null);
        Assertions.assertThat(side.getPreferredSize())
            .isEqualTo(new Dimension(widthConst, Integer.MAX_VALUE));
        Assertions.assertThat(side.getMaximumSize())
            .isEqualTo(new Dimension(widthConst, Integer.MAX_VALUE));
        Assertions.assertThat(side.getLayout()).isInstanceOf(BoxLayout.class);
    }

    @Test
    public void testSetLabelIn() throws Exception {
        //given
        SideNavComponent comp = new SideNavComponent(sampleEntries());
        JComponent side = new JPanel();

        //when
        method(SideNavComponent.class, "setLabelIn", JComponent.class).invoke(comp, side);

        //then
        Assertions.assertThat(side.getComponentCount()).isEqualTo(1);
        JComponent wrapper = (JComponent) side.getComponent(0);

        int expectedHeight = (int) field(SideNavComponent.class, "TITLE_WRAPPER_HEIGHT").get(null);
        Assertions.assertThat(wrapper.getPreferredSize())
            .isEqualTo(new Dimension(Integer.MAX_VALUE, expectedHeight));
        Assertions.assertThat(wrapper.getMaximumSize())
            .isEqualTo(new Dimension(Integer.MAX_VALUE, expectedHeight));
        Assertions.assertThat(wrapper.getBackground()).isEqualTo(Color.WHITE);
        Assertions.assertThat(wrapper.getLayout()).isInstanceOf(BorderLayout.class);

        EmptyBorder expectedBorder =
            (EmptyBorder) field(SideNavComponent.class, "TITLE_WRAPPER_BORDER").get(null);
        Assertions.assertThat(wrapper.getBorder()).isSameAs(expectedBorder);

        Assertions.assertThat(wrapper.getComponentCount()).isEqualTo(1);
        Assertions.assertThat(wrapper.getComponent(0)).isInstanceOf(JLabel.class);
        JLabel lbl = (JLabel) wrapper.getComponent(0);
        Assertions.assertThat(lbl.getText())
            .isEqualTo(field(SideNavComponent.class, "TITLE").get(null));
        int fontSizeConst = (int) field(SideNavComponent.class, "TITLE_FONTSIZE").get(null);
        Assertions.assertThat(lbl.getFont().getSize()).isEqualTo(fontSizeConst);
    }

    @Test
    public void testRenderSideNavItemsIn() throws Exception {
        //given
        Map<String, Runnable> entries = sampleEntries();
        SideNavComponent comp = new SideNavComponent(entries);

        JComponent parent = new JPanel();
        JPanel itemsPanel = new JPanel();

        final java.util.List<Object[]> ctorArgs = new java.util.ArrayList<>();

        try (@SuppressWarnings("unused")
        MockedConstruction<SideNavItemsComponent> mockedItems =
            Mockito.mockConstruction(SideNavItemsComponent.class, (mock, ctx) -> {
                ctorArgs.add(ctx.arguments().toArray());
                Mockito.when(mock.get()).thenReturn(itemsPanel);
            })) {

            //when
            method(SideNavComponent.class, "renderSideNavItemsIn", JComponent.class)
                .invoke(comp, parent);

            //then
            Assertions.assertThat(ctorArgs.size()).isEqualTo(1);
            Assertions.assertThat(ctorArgs.get(0)[0]).isSameAs(entries);
            Assertions.assertThat(parent.getComponentCount()).isEqualTo(1);
            Assertions.assertThat(parent.getComponent(0)).isSameAs(itemsPanel);
        }
    }
}