/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.task;

import org.apache.commons.io.FileUtils;
import org.jboss.installer.auto.InstallationDataSerializer;
import org.jboss.installer.core.InstallationData;
import org.jboss.installer.postinstall.TaskPrinter;
import org.jboss.installer.postinstall.server.DomainServer;
import org.jboss.installer.postinstall.server.StandaloneServer;
import org.jboss.installer.test.utils.MockLanguageUtils;
import org.jboss.installer.test.utils.TestServer;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.jboss.installer.test.utils.TestServer.TARGET_PATH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class JsfLibraryTaskTest {

    private static final String VERSION = "1.2.3.Final";
    @Rule
    public TemporaryFolder tempFolder = new TemporaryFolder();
    @ClassRule
    public static TestServer testServer = new TestServer();
    private StandaloneServer standaloneServer;
    private DomainServer domainServer;
    private InstallationData idata;
    private JsfLibraryTask.Config config;
    private Path tempFile;
    TaskPrinter printer = new NoopPrinter();

    @Before
    public void setUp() throws Exception {
        tempFile = tempFolder.newFile("a_file_impl.jar").toPath();
        standaloneServer = new StandaloneServer(TARGET_PATH);
        domainServer = new DomainServer(TARGET_PATH);
        idata = new InstallationData();
        idata.setTargetFolder(TARGET_PATH);
        config = new JsfLibraryTask.Config();
        idata.putConfig(config);
        config.setJsfVersion(VERSION);
    }

    @After
    public void tearDown() throws Exception {
        FileUtils.deleteDirectory(TARGET_PATH.resolve("modules").resolve("com").toFile());
        standaloneServer.close();
        domainServer.close();
    }

    @Test
    public void installJsfImplMojarraModule() throws Exception {
        config.setImplJarPath(tempFile.toString());
        config.setJsfProject(JsfLibraryTask.Config.JsfProject.Mojarra);

        new JsfLibraryTask().applyToInstallation(idata, printer);

        final Path modulePath = checkModuleDir("mojarra", "impl", VERSION, Paths.get("jakarta", "faces", "impl"));
        assertFileContains(modulePath, "name=\"jakarta.faces.impl:mojarra-1.2.3.Final\">");
        assertFileContains(modulePath, "<module name=\"jakarta.faces.api:mojarra-1.2.3.Final\"/>");
    }

    @Test
    public void installJsfApiMojarraModule() throws Exception {
        config.setJsfProject(JsfLibraryTask.Config.JsfProject.Mojarra);

        new JsfLibraryTask().applyToInstallation(idata, printer);

        final Path modulePath = checkModuleDir("mojarra", "api", VERSION, Paths.get( "jakarta", "faces", "api"));
        assertFileContains(modulePath, "xmlns=\"urn:jboss:module:1.9\" name=\"jakarta.faces.api:mojarra-1.2.3.Final\">");
        assertFileContains(modulePath, "<module name=\"jakarta.faces.impl:mojarra-1.2.3.Final\">");
    }

    @Test
    public void installJsfInjectionForMojarraModule() throws Exception {
        config.setJsfProject(JsfLibraryTask.Config.JsfProject.Mojarra);

        new JsfLibraryTask().applyToInstallation(idata, printer);
        final Path modulePath = TARGET_PATH.resolve("modules").resolve(Paths.get( "org", "jboss", "as", "jsf-injection"))
                .resolve("mojarra-1.2.3.Final");
        assertTrue(modulePath.toFile().exists());
        assertTrue(modulePath.resolve("module.xml").toFile().exists());
        final String[] files = modulePath.toFile().list((dir, name) -> name.endsWith(".jar"));
        for (String file : files) {
            if (!file.startsWith("weld-jsf") && !file.startsWith("wildfly-jsf-injection")) {
                fail("Unexpected file " + file);
            }
        }

        assertTrue(FileUtils.readFileToString(modulePath.resolve("module.xml").toFile(), "UTF-8").contains(files[0]));
        assertTrue(FileUtils.readFileToString(modulePath.resolve("module.xml").toFile(), "UTF-8").contains(files[1]));
        assertEquals(2, files.length);
    }

    @Test
    public void setNewJsfAsDefaultImplementation() throws Exception {
        config.setJsfProject(JsfLibraryTask.Config.JsfProject.Mojarra);
        config.setMakeDefault(true);

        new JsfLibraryTask().applyToInstallation(idata, printer);
    }

    @Test
    public void testSetAsDefaultOnDomain() throws Exception {
        config.setJsfProject(JsfLibraryTask.Config.JsfProject.Mojarra);
        config.setMakeDefault(true);

        domainServer.start("host.xml");
        try {
            assertTrue(new JsfDefaultLibraryTask().applyToDomain(idata, domainServer, printer));
            final String domainConfig = FileUtils.readFileToString(TARGET_PATH.resolve("domain/configuration/domain.xml").toFile(), "UTF-8");
            assertTrue(domainConfig.contains("default-jsf-impl-slot=\"mojarra-1.2.3.Final\""));
        } finally {
            domainServer.shutdown();
        }
    }

    @Test
    public void testIgnoreSetAsDefaultOnDomainPrimary() throws Exception {
        config.setJsfProject(JsfLibraryTask.Config.JsfProject.Mojarra);
        config.setMakeDefault(true);

        domainServer.start(DomainServer.HOST_PRIMARY_XML);
        try {
            assertTrue(new JsfDefaultLibraryTask().applyToDomain(idata, domainServer, printer));
            final String domainConfig = FileUtils.readFileToString(TARGET_PATH.resolve("domain/configuration/domain.xml").toFile(), "UTF-8");
            assertFalse(domainConfig.contains("default-jsf-impl-slot=\"mojarra-1.2.3.Final\""));
        } finally {
            domainServer.shutdown();
        }
    }

    @Test
    public void testIgnoreSetAsDefaultOnDomainSecondary() throws Exception {
        config.setJsfProject(JsfLibraryTask.Config.JsfProject.Mojarra);
        config.setMakeDefault(true);

        domainServer.start(DomainServer.HOST_SECONDARY_XML);
        try {
            assertTrue(new JsfDefaultLibraryTask().applyToDomain(idata, domainServer, printer));
            final String domainConfig = FileUtils.readFileToString(TARGET_PATH.resolve("domain/configuration/domain.xml").toFile(), "UTF-8");
            assertFalse(domainConfig.contains("default-jsf-impl-slot=\"mojarra-1.2.3.Final\""));
        } finally {
            domainServer.shutdown();
        }
    }

    @Test
    public void testSetAsDefaultOnStandalone() throws Exception {
        config.setJsfProject(JsfLibraryTask.Config.JsfProject.Mojarra);
        config.setMakeDefault(true);

        standaloneServer.start("standalone.xml");
        try {
            assertTrue(new JsfDefaultLibraryTask().applyToStandalone(idata, standaloneServer, printer));
            final String standaloneConfig = FileUtils.readFileToString(TARGET_PATH.resolve("standalone/configuration/standalone.xml").toFile(), "UTF-8");
            assertTrue(standaloneConfig.contains("default-jsf-impl-slot=\"mojarra-1.2.3.Final\""));
        } finally {
            standaloneServer.shutdown();
        }
    }

    @Test
    public void testUnsetDefaultJsfSlotIfNotNeededOnStandalone() throws Exception {
        config.setJsfProject(JsfLibraryTask.Config.JsfProject.MyFaces);
        config.setMakeDefault(false);

        final Path standaloneConfigFile = TARGET_PATH.resolve("standalone/configuration/standalone.xml");
        final String txt = FileUtils.readFileToString(standaloneConfigFile.toFile());
        FileUtils.writeStringToFile(standaloneConfigFile.toFile(), txt.replaceFirst("<subsystem xmlns=\"urn:jboss:domain:jsf:(\\d+\\.\\d+)\"/>",
                "<subsystem xmlns=\"urn:jboss:domain:jsf:$1\" default-jsf-impl-slot=\"myfaces\"/>"));

        standaloneServer.start("standalone.xml");
        try {
            assertTrue(new JsfDefaultLibraryTask().applyToStandalone(idata, standaloneServer, printer));
            final String standaloneConfig = FileUtils.readFileToString(standaloneConfigFile.toFile(), "UTF-8");
            assertThat(standaloneConfig)
                    .doesNotContain("default-jsf-impl-slot=\"myfaces\"");
        } finally {
            standaloneServer.shutdown();
        }
    }

    @Test
    public void testSerializeConfig() throws Exception {
        final JsfLibraryTask.Config config = new JsfLibraryTask.Config();
        config.setImplJarPath(tempFolder.newFile("impl.jar").toString());
        config.setJsfProject(JsfLibraryTask.Config.JsfProject.MyFaces);
        idata.putConfig(config);

        final InstallationDataSerializer serializer = new InstallationDataSerializer(new MockLanguageUtils());
        final Path outputFile = tempFolder.newFile().toPath();
        serializer.serialize(idata, outputFile);

        final JsfLibraryTask.Config deserializedConfig = serializer.deserialize(outputFile, Optional.empty()).getConfig(JsfLibraryTask.Config.class);

        assertEquals(config, deserializedConfig);
    }

    private Path checkModuleDir(String project, String type, String version, Path prefix) {
        final String moduleName = type.equals("injection")?"jsf-injection":String.format("%s-%s", project, version);
        final Path modulePath = TARGET_PATH.resolve("modules").resolve(prefix).resolve(moduleName);
        assertTrue(modulePath + " doesn't exist", modulePath.toFile().exists());
        assertTrue(modulePath.resolve("module.xml").toFile().exists());
        if ("impl".equals(type)) {
            assertTrue(modulePath.resolve(String.format("jakarta.faces-%s.jar", version)).toFile().exists());
        }
        return modulePath;
    }

    private void assertFileContains(Path modulePath, String pattern) throws IOException {
        String msg = String.format("Expecting to find [%s] in %s, but was:%n%s", pattern, modulePath, Files.readString(modulePath.resolve("module.xml")));
        assertTrue(msg, Files.readAllLines(modulePath.resolve("module.xml")).stream()
                .anyMatch(l->l.contains(pattern)));
    }

}