package de.renew.navigator.gui;

import java.util.List;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedConstruction;
import org.mockito.junit.jupiter.MockitoExtension;

import de.renew.navigator.FilesystemController;
import de.renew.navigator.NavigatorExtension;
import de.renew.navigator.NavigatorPlugin;
import de.renew.navigator.models.TreeElement;
import de.renew.windowmanagement.Workbench;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockConstruction;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;


@ExtendWith(MockitoExtension.class)
class TreeExpansionListenerTest {

    private NavigatorGuiProxy proxy;
    private NavigatorExtension ext1;
    private NavigatorExtension ext2;

    private MockedConstruction<NavigatorGuiImpl> mockConstruction;

    @BeforeEach
    void setUp() {
        FilesystemController fsMock = mock(FilesystemController.class);
        Workbench wbMock = mock(Workbench.class);
        NavigatorPlugin pluginMock = mock(NavigatorPlugin.class);
        ext1 = mock(NavigatorExtension.class);
        ext2 = mock(NavigatorExtension.class);

        mockConstruction = mockConstruction(NavigatorGuiImpl.class, (mock, context) -> {
        });

        proxy = new NavigatorGuiProxy(fsMock, wbMock, pluginMock);
    }

    @AfterEach
    void tearDown() {
        mockConstruction.close();
    }

    @Test
    void addAndGetExtensionsBeforeInit() {
        proxy.addExtension(ext1);
        proxy.addExtension(ext2);

        List<NavigatorExtension> temp = proxy.getExtensions();
        assertEquals(2, temp.size());
        assertTrue(temp.contains(ext1));
        assertTrue(temp.contains(ext2));
    }

    @Test
    void openWindowInitializesProxyAndTransfersExtensions() {
        proxy.addExtension(ext1);

        proxy.openWindow();

        NavigatorGuiImpl implMock = mockConstruction.constructed().get(0);

        verify(implMock).openWindow();
        verify(implMock).addExtension(ext1);

        assertTrue(proxy.getExtensions().isEmpty());
    }

    @Test
    void removeExtensionBeforeInit() {
        proxy.addExtension(ext1);
        proxy.addExtension(ext2);

        assertTrue(proxy.removeExtension(ext1));

        List<NavigatorExtension> temp = proxy.getExtensions();
        assertEquals(1, temp.size());
        assertFalse(temp.contains(ext1));
        assertTrue(temp.contains(ext2));
    }

    @Test
    void removeExtensionAfterInitDelegatesToImpl() {
        proxy.openWindow();
        NavigatorGuiImpl implMock = mockConstruction.constructed().get(0);

        when(implMock.removeExtension(ext1)).thenReturn(true);

        assertTrue(proxy.removeExtension(ext1));
        verify(implMock).removeExtension(ext1);
    }

    @Test
    void closeWindowWithoutInitDoesNothing() {
        assertDoesNotThrow(() -> proxy.closeWindow());
    }

    @Test
    void closeWindowAfterInitDelegatesAndClearsProxy() {
        proxy.openWindow();
        NavigatorGuiImpl implMock = mockConstruction.constructed().get(0);

        proxy.closeWindow();
        verify(implMock).closeWindow();

        assertNotNull(proxy.getExtensions());
        assertTrue(proxy.getExtensions().isEmpty());
    }

    @Test
    void delegationOfOtherMethods() {
        proxy.openWindow();
        NavigatorGuiImpl implMock = mockConstruction.constructed().get(0);

        var renderer = mock(DefaultFileTreeCellRenderer.class);
        var tree = new JTree();
        var root = new DefaultMutableTreeNode("root");
        var sel = List.of(mock(TreeElement.class));

        when(implMock.getTreeCellRenderer()).thenReturn(renderer);
        when(implMock.getTree()).thenReturn(tree);
        when(implMock.getRootNode()).thenReturn(root);
        when(implMock.getSelectedElements()).thenReturn(sel);

        assertSame(renderer, proxy.getTreeCellRenderer());
        assertSame(tree, proxy.getTree());
        assertSame(root, proxy.getRootNode());
        assertSame(sel, proxy.getSelectedElements());

        proxy.collapseAll();
        proxy.expand();
        proxy.removeSelectedNodes();

        verify(implMock).collapseAll();
        verify(implMock).expand();
        verify(implMock).removeSelectedNodes();
    }
}
