/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2021 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.installer.postinstall;

import org.jboss.installer.core.InstallationData;
import org.jboss.installer.core.InstallationFailedException;
import org.jboss.installer.core.LanguageUtils;
import org.jboss.installer.postinstall.server.DomainServer;
import org.jboss.installer.postinstall.server.StandaloneServer;
import org.jboss.installer.test.utils.MockLanguageUtils;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.InOrder;

import java.beans.PropertyChangeSupport;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.TreeSet;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

public class PostInstallTaskRunnerTest {

    @Rule
    public TemporaryFolder temp = new TemporaryFolder();

    private PropertyChangeSupport propertyChangeSupport = mock(PropertyChangeSupport.class);
    private LanguageUtils langUtils = new MockLanguageUtils();

    @Test
    public void executeSimpleTask() throws Exception {
        final SimplePostInstallTask mock = mockSimpleTask();
        final StandaloneServer mockServer = mock(StandaloneServer.class);
        final InstallationData installationData = registerTasks(mock);

        new PostInstallTaskRunner(mockServer, null, langUtils, null).execute(installationData, propertyChangeSupport);

        verify(mock).applyToInstallation(eq(installationData), any());
    }

    @Test
    public void executeSimpleTaskLogsTaskName() throws Exception {
        final SimplePostInstallTask mock = mockSimpleTask();
        when(mock.getName()).thenReturn("test_value");
        final StandaloneServer mockServer = mock(StandaloneServer.class);
        final InstallationData installationData = registerTasks(mock);

        new PostInstallTaskRunner(mockServer, null, langUtils, null).execute(installationData, propertyChangeSupport);

        verify(propertyChangeSupport).firePropertyChange(PostInstallTaskRunner.POST_INSTALL_TASK_NAME_PROPERTY, "", "test_value");
        verify(mock).applyToInstallation(eq(installationData), any());
    }

    @Test
    public void executeCliTask() throws Exception {
        final CliPostInstallTask mockTask = mockCliTask();
        final StandaloneServer mockServer = mockStandaloneServer("config1");
        final InstallationData installationData = registerTasks(mockTask);

        new PostInstallTaskRunner(mockServer, null, langUtils, null).execute(installationData, propertyChangeSupport);

        verify(mockTask).applyToStandalone(eq(installationData), eq(mockServer), any());
    }

    @Test
    public void cleanUpAfterExecuteCliTask() throws Exception {
        final CliPostInstallTask mockTask = mockCliTask();
        final StandaloneServer mockServer = mockStandaloneServer("config1");
        final InstallationData installationData = registerTasks(mockTask);

        final Path mockServerDir = temp.newFolder("mock-server").toPath();
        final Path testPath = mockServerDir.resolve("standalone").resolve("data");
        Files.createDirectories(testPath);
        Files.writeString(testPath.resolve("test"), "test");
        when(mockServer.getTemporaryPaths()).thenReturn(List.of(testPath));

        new PostInstallTaskRunner(mockServer, null, langUtils, null).execute(installationData, propertyChangeSupport);

        assertThat(testPath).doesNotExist();
    }

    @Test
    public void simpleTasksAreExecutedBeforeCliTask() throws Exception {
        final CliPostInstallTask mockCliTask1 = mockCliTask();
        final CliPostInstallTask mockCliTask2 = mockCliTask();
        final SimplePostInstallTask mockSimpleTask1 = mockSimpleTask();
        final SimplePostInstallTask mockSimpleTask2 = mockSimpleTask();
        final StandaloneServer mockServer = mockStandaloneServer("config1");
        final InstallationData installationData = registerTasks(mockCliTask1, mockCliTask2, mockSimpleTask1, mockSimpleTask2);

        new PostInstallTaskRunner(mockServer, null, langUtils, null).execute(installationData, propertyChangeSupport);

        InOrder inOrder = inOrder(mockCliTask1, mockSimpleTask1, mockCliTask2, mockSimpleTask2);
        inOrder.verify(mockSimpleTask1).applyToInstallation(eq(installationData), any());
        inOrder.verify(mockSimpleTask2).applyToInstallation(eq(installationData), any());
        inOrder.verify(mockCliTask1).applyToStandalone(eq(installationData), eq(mockServer), any());
        inOrder.verify(mockCliTask2).applyToStandalone(eq(installationData), eq(mockServer), any());
    }

    @Test
    public void unknownTasksThrowException() throws Exception {
        final SimplePostInstallTask simpleTask = mockSimpleTask();
        final PostInstallTask aTask = mock(PostInstallTask.class);
        // need to mock the InstallationData, otherwise we won't be able to pass wrong tasks in
        final InstallationData installationData = mock(InstallationData.class);
        when(installationData.getPostInstallTasks()).thenReturn(Arrays.asList(simpleTask, aTask));

        try {
            new PostInstallTaskRunner(null, null, langUtils, null).execute(installationData, propertyChangeSupport);
            fail("Should fail on unknown task");
        } catch (IllegalStateException e) {
            Assert.assertEquals("Unknown task type", e.getMessage());
        }

        verify(simpleTask).setLanguageUtils(any());
        verifyNoMoreInteractions(simpleTask);
    }

    @Test
    public void serverIsStartedBeforeExecutingCliTasks() throws Exception {
        final CliPostInstallTask mockTask = mockCliTask();
        final StandaloneServer mockServer = mockStandaloneServer("config1");
        final InstallationData installationData = registerTasks(mockTask);

        new PostInstallTaskRunner(mockServer, null, langUtils, null).execute(installationData, propertyChangeSupport);

        InOrder inOrder = inOrder(mockServer, mockTask);
        inOrder.verify(mockServer).start(any(String.class));
        inOrder.verify(mockTask).applyToStandalone(eq(installationData), eq(mockServer), any());
        inOrder.verify(mockServer).shutdown();
    }

    @Test
    public void cliTaskIsCalledForEachServerConfiguration() throws Exception {
        final CliPostInstallTask mockTask = mockCliTask();
        final StandaloneServer mockServer = mockStandaloneServer("config1", "config2");
        final InstallationData installationData = registerTasks(mockTask);

        new PostInstallTaskRunner(mockServer, null, langUtils, null).execute(installationData, propertyChangeSupport);

        InOrder inOrder = inOrder(mockServer, mockTask);
        inOrder.verify(mockServer).start("config1");
        inOrder.verify(mockTask).applyToStandalone(eq(installationData), eq(mockServer), any());
        inOrder.verify(mockServer).start("config2");
        inOrder.verify(mockTask).applyToStandalone(eq(installationData), eq(mockServer), any());
    }

    @Test
    public void cliTaskCalledForDomainServer() throws Exception {
        final CliPostInstallTask mockTask = mockCliTask();
        final DomainServer mockServer = mockDomainServer("config1");
        final InstallationData installationData = registerTasks(mockTask);

        new PostInstallTaskRunner(null, mockServer, langUtils, null).execute(installationData, propertyChangeSupport);

        verify(mockTask).applyToDomain(eq(installationData), eq(mockServer), any());
    }

    @Test
    public void failedCliTaskPreventsOtherTasks() throws Exception {
        final CliPostInstallTask mockCliTask1 = mock(CliPostInstallTask.class);
        when(mockCliTask1.applyToStandalone(any(), any(), any())).thenReturn(false);
        final CliPostInstallTask mockCliTask2 = mockCliTask();

        final StandaloneServer mockServer = mockStandaloneServer("config1");
        final InstallationData installationData = registerTasks(mockCliTask1, mockCliTask2);

        try {
            new PostInstallTaskRunner(mockServer, null, langUtils, null).execute(installationData, propertyChangeSupport);
            fail("Exception should have been thrown");
        } catch (InstallationFailedException e) {
            // OK, expected
        }

        verify(mockCliTask2).setLanguageUtils(any());
        verifyNoMoreInteractions(mockCliTask2);
    }

    @Test
    public void failedSimpleTaskPreventsOtherTasks() throws Exception {
        final SimplePostInstallTask mockSimpleTask1 = mockSimpleTask();
        final CliPostInstallTask mockCliTask2 = mockCliTask();
        when(mockSimpleTask1.applyToInstallation(any(), any())).thenReturn(false);

        final StandaloneServer mockServer = mockStandaloneServer("config1");
        final InstallationData installationData = registerTasks(mockSimpleTask1, mockCliTask2);

        try {
            new PostInstallTaskRunner(mockServer, null, langUtils, null).execute(installationData, propertyChangeSupport);
            fail("An exception should have been raised");
        } catch (InstallationFailedException e) {
            // OK we're expecting this
        }

        verify(mockCliTask2).setLanguageUtils(any());
        verifyNoMoreInteractions(mockCliTask2);
    }

    private CliPostInstallTask mockCliTask() {
        final CliPostInstallTask mock = mock(CliPostInstallTask.class);
        when(mock.applyToStandalone(any(), any(), any())).thenReturn(true);
        when(mock.applyToDomain(any(), any(), any())).thenReturn(true);
        return mock;
    }

    private SimplePostInstallTask mockSimpleTask() {
        final SimplePostInstallTask mock = mock(SimplePostInstallTask.class);
        when(mock.applyToInstallation(any(), any())).thenReturn(true);
        return mock;
    }

    private StandaloneServer mockStandaloneServer(String... configs) {
        final StandaloneServer mockServer = mock(StandaloneServer.class);
        when(mockServer.availableConfigurations()).thenReturn(new TreeSet<>(Arrays.asList(configs)));
        when(mockServer.getTemporaryPaths()).thenReturn(Collections.emptyList());
        return mockServer;
    }

    private DomainServer mockDomainServer(String... configs) {
        final DomainServer mockServer = mock(DomainServer.class);
        when(mockServer.availableConfigurations()).thenReturn(new TreeSet<>(Arrays.asList(configs)));
        when(mockServer.getTemporaryPaths()).thenReturn(Collections.emptyList());
        return mockServer;
    }

    private InstallationData registerTasks(PostInstallTask... tasks) {
        final InstallationData installationData = new InstallationData();
        for (PostInstallTask task : tasks) {
            // cast it to correct type so that we don't need to register methods
            if (task instanceof SimplePostInstallTask) {
                installationData.addPostInstallTask((SimplePostInstallTask)task);
            } else {
                installationData.addPostInstallTask((CliPostInstallTask)task);
            }
        }
        return installationData;
    }
}
