package completer;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import de.renew.console.completer.LocationsCompleter;
import de.renew.plugin.IPlugin;
import de.renew.plugin.PluginManager;
import de.renew.plugin.PluginProperties;
import de.renew.util.StringUtil;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;



class LocationsCompleterTest {

    private static final String PLUGIN1_NAME = "plugin1";
    private static final String PLUGIN2_NAME = "plugin2";
    private static final String PLUGIN3_NAME = "plugin3";
    private static final String PLUGIN1_PATH = "file:/path/to/plugin1.jar";
    private static final String PLUGIN2_PATH = "file:/path/to/plugin2.jar";
    private static final String PLUGIN3_PATH = "file:/path/to/plugin3.jar";

    private IPlugin _mockPlugin1;
    private IPlugin _mockPlugin2;
    private IPlugin _mockPlugin3;

    @BeforeEach
    void setUp() throws MalformedURLException {
        _mockPlugin1 = createMockPlugin(PLUGIN1_NAME, PLUGIN1_PATH);
        _mockPlugin2 = createMockPlugin(PLUGIN2_NAME, PLUGIN2_PATH);
        _mockPlugin3 = createMockPlugin(PLUGIN3_NAME, PLUGIN3_PATH);
    }

    private IPlugin createMockPlugin(String name, String url) throws MalformedURLException {
        IPlugin plugin = mock(IPlugin.class);
        PluginProperties props = mock(PluginProperties.class);

        when(plugin.getName()).thenReturn(name);
        when(plugin.getProperties()).thenReturn(props);
        when(props.getURL()).thenReturn(new URL(url));

        return plugin;
    }

    @Test
    void testCompleteAddsAllPluginPaths() {
        try (MockedStatic<PluginManager> mockedPluginManager =
            Mockito.mockStatic(PluginManager.class)) {
            // given
            PluginManager mockManager = mock(PluginManager.class);
            when(mockManager.getPlugins())
                .thenReturn(Arrays.asList(_mockPlugin1, _mockPlugin2, _mockPlugin3));
            when(mockManager.getPluginByName(anyString())).thenAnswer(invocation -> {
                String name = invocation.getArgument(0);
                return switch (name) {
                    case PLUGIN1_NAME -> _mockPlugin1;
                    case PLUGIN2_NAME -> _mockPlugin2;
                    case PLUGIN3_NAME -> _mockPlugin3;
                    default -> null;
                };
            });
            mockedPluginManager.when(PluginManager::getInstance).thenReturn(mockManager);

            LocationsCompleter completer = new LocationsCompleter();
            List<CharSequence> candidates = new ArrayList<>();

            // when
            completer.complete("", 0, candidates);

            // then
            assertAll(
                () -> assertEquals(3, candidates.size()),
                () -> assertContainsExtendedPath(candidates, PLUGIN1_PATH),
                () -> assertContainsExtendedPath(candidates, PLUGIN2_PATH),
                () -> assertContainsExtendedPath(candidates, PLUGIN3_PATH));
        }
    }

    @Test
    void testCompleteFiltersByJarFilename() {
        try (MockedStatic<PluginManager> mockedPluginManager =
            Mockito.mockStatic(PluginManager.class)) {
            // given
            PluginManager mockManager = mock(PluginManager.class);
            when(mockManager.getPlugins())
                .thenReturn(Arrays.asList(_mockPlugin1, _mockPlugin2, _mockPlugin3));
            when(mockManager.getPluginByName(anyString())).thenAnswer(invocation -> {
                String name = invocation.getArgument(0);
                if (name.equals(PLUGIN2_NAME))
                    return _mockPlugin2;
                return null;
            });
            mockedPluginManager.when(PluginManager::getInstance).thenReturn(mockManager);

            LocationsCompleter completer = new LocationsCompleter();
            List<CharSequence> candidates = new ArrayList<>();

            // when
            completer.complete("plugin2.jar", 0, candidates);

            // then
            assertAll(
                () -> assertEquals(1, candidates.size()),
                () -> assertContainsExtendedPath(candidates, PLUGIN2_PATH),
                () -> assertDoesNotContainExtendedPath(candidates, PLUGIN1_PATH),
                () -> assertDoesNotContainExtendedPath(candidates, PLUGIN3_PATH));
        }
    }

    @Test
    void testCompleteReturnsCorrectIndex() {
        try (MockedStatic<PluginManager> mockedPluginManager =
            Mockito.mockStatic(PluginManager.class)) {
            // given
            PluginManager mockManager = mock(PluginManager.class);
            when(mockManager.getPlugins())
                .thenReturn(Arrays.asList(_mockPlugin1, _mockPlugin2, _mockPlugin3));
            when(mockManager.getPluginByName(anyString())).thenAnswer(invocation -> {
                String name = invocation.getArgument(0);
                return switch (name) {
                    case PLUGIN1_NAME -> _mockPlugin1;
                    case PLUGIN2_NAME -> _mockPlugin2;
                    case PLUGIN3_NAME -> _mockPlugin3;
                    default -> null;
                };
            });
            mockedPluginManager.when(PluginManager::getInstance).thenReturn(mockManager);

            LocationsCompleter completer = new LocationsCompleter();
            List<CharSequence> candidates = new ArrayList<>();

            // when
            int index = completer.complete("", 0, candidates);

            // then
            assertEquals(0, index);
            assertEquals(3, candidates.size());
        }
    }

    @Test
    void testCompleteWithNoPlugins() {
        try (MockedStatic<PluginManager> mockedPluginManager =
            Mockito.mockStatic(PluginManager.class)) {
            // given
            PluginManager mockManager = mock(PluginManager.class);
            when(mockManager.getPlugins()).thenReturn(Collections.emptyList());
            when(mockManager.getPluginByName(anyString())).thenReturn(null);
            mockedPluginManager.when(PluginManager::getInstance).thenReturn(mockManager);

            LocationsCompleter completer = new LocationsCompleter();
            List<CharSequence> candidates = new ArrayList<>();

            // when
            int index = completer.complete("", 0, candidates);

            // then
            assertEquals(-1, index);
            assertTrue(candidates.isEmpty());
        }
    }

    @Test
    void testCompleteFiltersByNamePrefix() {
        try (MockedStatic<PluginManager> mockedPluginManager =
            Mockito.mockStatic(PluginManager.class)) {
            // given
            PluginManager mockManager = mock(PluginManager.class);
            when(mockManager.getPlugins())
                .thenReturn(Arrays.asList(_mockPlugin1, _mockPlugin2, _mockPlugin3));
            when(mockManager.getPluginByName(anyString())).thenAnswer(invocation -> {
                String name = invocation.getArgument(0);
                if (name.equals(PLUGIN2_NAME))
                    return _mockPlugin2;
                return null;
            });
            mockedPluginManager.when(PluginManager::getInstance).thenReturn(mockManager);

            LocationsCompleter completer = new LocationsCompleter();
            List<CharSequence> candidates = new ArrayList<>();

            // when
            completer.complete("plugin2", 0, candidates);

            // then
            assertAll(
                () -> assertEquals(1, candidates.size()),
                () -> assertContainsExtendedPath(candidates, PLUGIN2_PATH));
        }
    }

    @Test
    void testGetPluginLocationsReturnsAll() {
        try (MockedStatic<PluginManager> mockedPluginManager =
            Mockito.mockStatic(PluginManager.class)) {
            // given
            PluginManager mockManager = mock(PluginManager.class);
            when(mockManager.getPlugins())
                .thenReturn(Arrays.asList(_mockPlugin1, _mockPlugin2, _mockPlugin3));
            when(mockManager.getPluginByName(anyString())).thenAnswer(invocation -> {
                String name = invocation.getArgument(0);
                return switch (name) {
                    case PLUGIN1_NAME -> _mockPlugin1;
                    case PLUGIN2_NAME -> _mockPlugin2;
                    case PLUGIN3_NAME -> _mockPlugin3;
                    default -> null;
                };
            });
            mockedPluginManager.when(PluginManager::getInstance).thenReturn(mockManager);

            LocationsCompleter completer = new LocationsCompleter();

            // when
            List<String> locations = completer.getPluginLocations();

            // then
            assertAll(
                () -> assertEquals(3, locations.size()),
                () -> assertContainsExtendedPath(locations, PLUGIN1_PATH),
                () -> assertContainsExtendedPath(locations, PLUGIN2_PATH),
                () -> assertContainsExtendedPath(locations, PLUGIN3_PATH));
        }
    }

    @Test
    void testGetPluginLocationsPluginByNameReturnsNull() {
        try (MockedStatic<PluginManager> mockedPluginManager =
            Mockito.mockStatic(PluginManager.class)) {
            // given
            PluginManager mockManager = mock(PluginManager.class);
            IPlugin mockPlugin = mock(IPlugin.class);
            when(mockPlugin.getName()).thenReturn("missingPlugin");
            when(mockManager.getPlugins()).thenReturn(List.of(mockPlugin));
            when(mockManager.getPluginByName("missingPlugin")).thenReturn(null);
            mockedPluginManager.when(PluginManager::getInstance).thenReturn(mockManager);

            LocationsCompleter completer = new LocationsCompleter();

            // when
            List<String> locations = completer.getPluginLocations();

            // then
            assertTrue(locations.isEmpty());
        }
    }

    @Test
    void testGetPluginLocationsEmptyPluginsList() {
        try (MockedStatic<PluginManager> mockedPluginManager =
            Mockito.mockStatic(PluginManager.class)) {
            // given
            PluginManager mockManager = mock(PluginManager.class);
            when(mockManager.getPlugins()).thenReturn(new ArrayList<>());
            mockedPluginManager.when(PluginManager::getInstance).thenReturn(mockManager);

            LocationsCompleter completer = new LocationsCompleter();

            // when
            List<String> locations = completer.getPluginLocations();

            // then
            assertTrue(locations.isEmpty());
        }
    }

    private void assertContainsExtendedPath(
        List<? extends CharSequence> actual, String expectedPath)
    {
        String extended = StringUtil.getExtendedFilename(expectedPath);
        assertTrue(
            actual.contains(extended), "Expected path '" + extended + "' not found in: " + actual);
    }

    private void assertDoesNotContainExtendedPath(
        List<? extends CharSequence> actual, String unexpectedPath)
    {
        String extended = StringUtil.getExtendedFilename(unexpectedPath);
        assertFalse(
            actual.contains(extended), "Unexpected path '" + extended + "' found in: " + actual);
    }
}