package org.jboss.installer.postinstall.task.impl;

import org.jboss.installer.core.InstallationData;
import org.jboss.installer.core.BuildProperties;
import org.jboss.installer.postinstall.TaskPrinter;
import org.jboss.installer.postinstall.task.JSFLibraryConfig;
import org.jboss.installer.postinstall.task.NoopPrinter;
import org.jboss.installer.postinstall.task.jsf.MyFacesJsfLibrarySetup;
import org.jboss.installer.test.utils.MavenUtils;
import org.jboss.installer.test.utils.MockLanguageUtils;
import org.jboss.installer.test.utils.TestServer;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.wildfly.channel.Channel;
import org.wildfly.channel.ChannelManifest;
import org.wildfly.channel.ChannelManifestMapper;
import org.wildfly.channel.ChannelMapper;
import org.wildfly.channel.Stream;
import org.wildfly.prospero.metadata.ProsperoMetadataUtils;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.jboss.installer.postinstall.task.jsf.MyFacesJsfLibrarySetup.MYFACES_API_ARTIFACT_ID;
import static org.jboss.installer.postinstall.task.jsf.MyFacesJsfLibrarySetup.MYFACES_GROUP_ID;
import static org.jboss.installer.postinstall.task.jsf.MyFacesJsfLibrarySetup.MYFACES_IMPL_ARTIFACT_ID;
import static org.wildfly.common.Assert.assertTrue;

public class MyFacesJsfLibrarySetupTest {

    @Rule
    public TestServer testServer = new TestServer();

    @Rule
    public TemporaryFolder temp = new TemporaryFolder();

    private TaskPrinter printer = new NoopPrinter();

    private final MockLanguageUtils langUtils = new MockLanguageUtils();

    @Before
    public void setUp() throws Exception {
        // the myfaces FP is not part of wildfly channel, upstream we need to create a new channel
        if (BuildProperties.IS_WILDFLY_INSTALLATION) {
            final Path manifestFile = temp.newFile("myfaces-manifest.yaml").toPath();
            final ChannelManifest manifest = new ChannelManifest("1.0.0", null, null, null, null,
                    List.of(
                            new Stream("org.wildfly", "wildfly-myfaces-feature-pack", "2.0.0.Final"),
                            new Stream("org.wildfly", "myfaces-injection", "2.0.0.Final")
                    ));
            Files.writeString(manifestFile, ChannelManifestMapper.toYaml(manifest));

            final Path channelsFile = TestServer.TARGET_PATH.resolve(ProsperoMetadataUtils.METADATA_DIR).resolve(ProsperoMetadataUtils.INSTALLER_CHANNELS_FILE_NAME);

            final List<Channel> channel = ChannelMapper.fromString(Files.readString(channelsFile));
            final ArrayList<Channel> res = new ArrayList<>(channel);
            res.add(new Channel.Builder()
                    .setName("myfaces-fp")
                    .setManifestUrl(manifestFile.toUri().toURL())
                    .addRepository("central", "https://repo1.maven.org/maven2/")
                    .build());
            Files.writeString(channelsFile, ChannelMapper.toYaml(res));
        }
    }

    @Test
    public void createNewChannelAndInstall() throws Exception {
        final String version = "4.0.1";
        final String repositoryUrl = "https://repo1.maven.org/maven2/";

        // set up test data
        final JSFLibraryConfig config = new JSFLibraryConfig();
        config.setJsfVersion(version);
        config.setRemoteMavenRepositoryUrl(new URL(repositoryUrl));
        config.setLocalChannelRepositoryPath(TestServer.TARGET_PATH.resolve("jsf-repo").toString());
        config.setJsfProject(JSFLibraryConfig.JsfProject.MyFaces);
        final InstallationData installationData = new InstallationData();
        installationData.putConfig(config);
        installationData.setTargetFolder(TestServer.TARGET_PATH);

        // install feature pack
        final JsfLibraryTask jsfLibraryTask = new JsfLibraryTask();
        jsfLibraryTask.setLanguageUtils(langUtils);
        assertTrue(jsfLibraryTask.applyToInstallation(installationData, printer));

        verifyManifestDeployed(TestServer.TARGET_PATH.resolve("jsf-repo"), version);
        verifyMyFacesFeaturePackInstalled(version);
        verifyMyFacesChannelIsRegistered(repositoryUrl);
    }

    @Test
    public void useExistingChannelAndInstall() throws Exception {
        final String version = "4.0.1";
        final String repositoryUrl = "https://repo1.maven.org/maven2/";
        final File tempRepo = temp.newFolder("myfaces-repo");
        final String localRepositoryUrl = tempRepo.toURI().toURL().toExternalForm();

        final MyFacesJsfLibrarySetup myFacesJsfLibrarySetup = new MyFacesJsfLibrarySetup(TestServer.TARGET_PATH, langUtils);
        // deploy the manifest definition
        final String manifestCoordinates = myFacesJsfLibrarySetup.createChannelManifest(version, tempRepo.toPath().toString());
        MavenUtils.deployMyFacesArtifactsLocally(version, repositoryUrl, localRepositoryUrl);
        verifyManifestDeployed(tempRepo.toPath(), version);

        // set up test data
        final JSFLibraryConfig config = new JSFLibraryConfig();
        config.setManifestCoordinates(manifestCoordinates);
        config.setRemoteMavenRepositoryUrl(new URL(localRepositoryUrl));
        config.setJsfProject(JSFLibraryConfig.JsfProject.MyFaces);
        final InstallationData installationData = new InstallationData();
        installationData.putConfig(config);
        installationData.setTargetFolder(TestServer.TARGET_PATH);

        // install feature pack
        final JsfLibraryTask jsfLibraryTask = new JsfLibraryTask();
        jsfLibraryTask.setLanguageUtils(langUtils);
        assertTrue(jsfLibraryTask.applyToInstallation(installationData, printer));

        verifyMyFacesFeaturePackInstalled(version);
        verifyMyFacesChannelIsRegistered(localRepositoryUrl);
    }

    private static void verifyMyFacesFeaturePackInstalled(String version) {
        // verify - new modules present
        final Path jakartaModulePath = TestServer.TARGET_PATH.resolve("modules").resolve("jakarta").resolve("faces");
        assertModuleExists(jakartaModulePath, "api", version);
        assertModuleExists(jakartaModulePath, "impl", version);

        // verify - jsf module set in standalone.xml
        final Path standaloneConfigPath = TestServer.TARGET_PATH.resolve("standalone").resolve("configuration").resolve("standalone.xml");
        assertThat(standaloneConfigPath)
                .content()
                .containsPattern("<subsystem xmlns=\"urn:jboss:domain:jsf:[0-9\\.]+\" default-jsf-impl-slot=\"myfaces\"/>");
    }

    private static void verifyMyFacesChannelIsRegistered(String localRepositoryUrl) throws IOException {
        final Path channelsPath = TestServer.TARGET_PATH.resolve(".installation").resolve("installer-channels.yaml");
        assertThat(channelsPath)
                .exists()
                .isNotEmptyFile();
        final List<Channel> channelDefinitions = ChannelMapper.fromString(Files.readString(channelsPath));
        assertThat(channelDefinitions)
                .anyMatch(c->c.getName().equals("MyFaces channel") &&
                        c.getRepositories().stream().anyMatch(r->r.getUrl().equals(localRepositoryUrl)));
    }

    private static void verifyManifestDeployed(Path tempRepo, String version) throws MalformedURLException {
        final Path manifestRepoPath = Path.of("org", "apache", "myfaces", "channel", "myfaces", "1.0.0");
        final Path myfacesManifestFile = tempRepo.resolve(manifestRepoPath)
                .resolve("myfaces-1.0.0-manifest.yaml");
        assertThat(myfacesManifestFile)
                .exists()
                .isNotEmptyFile();
        final ChannelManifest channelManifest = ChannelManifestMapper.from(myfacesManifestFile.toUri().toURL());
        assertThat(channelManifest.getStreams())
                .contains(new Stream(MYFACES_GROUP_ID, MYFACES_API_ARTIFACT_ID, version),
                        new Stream(MYFACES_GROUP_ID, MYFACES_IMPL_ARTIFACT_ID, version));
    }

    private static void assertModuleExists(Path jakartaModulePath, String impl, String version) {
        final Path implModulePath = jakartaModulePath.resolve(impl).resolve("myfaces");
        assertThat(implModulePath)
                .exists()
                .isDirectory();
        assertThat(implModulePath.resolve(String.format("myfaces-%s-%s.jar", impl, version)))
                .exists()
                .isNotEmptyFile();
    }

}