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.EmbeddedServer;
import org.jboss.installer.postinstall.server.StandaloneServer;
import org.jboss.installer.test.utils.MockInputConsole;
import org.jboss.installer.test.utils.MockLanguageUtils;
import org.jboss.installer.test.utils.MockLdapServer;
import org.jboss.installer.test.utils.ServerUtils;
import org.jboss.installer.test.utils.StandaloneXmlUtil;
import org.jboss.installer.test.utils.TestServer;
import org.jboss.installer.validators.LDAPValidatorTest;
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 org.wildfly.core.launcher.ProcessHelper;

import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Base64;
import java.util.Optional;

import static org.jboss.installer.test.utils.TestServer.TARGET_PATH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class LDAPSetupTaskTest {

    protected static final String LDAP_REALM_NAME = "my-ldap-realm";
    @ClassRule
    public static TestServer testServer = new TestServer();
    @Rule
    public TemporaryFolder tempFolder = new TemporaryFolder();
    private MockLdapServer ldapServer;

    private StandaloneServer standaloneServer;
    private DomainServer domainServer;
    private InstallationData idata;
    private TaskPrinter printer = new NoopPrinter();
    private LDAPSetupTask.Config config;

    @Before
    public void setUp() throws Exception {
        standaloneServer = new StandaloneServer(TARGET_PATH);
        domainServer = new DomainServer(TARGET_PATH);
        idata = new InstallationData();
        idata.setTargetFolder(TARGET_PATH);

        ldapServer = new MockLdapServer(tempFolder.getRoot());
        ldapServer.startServer();
        ldapServer.addUser("cn=John Brown,o=TestOrganization","jbrown","John Brown", "Brown");

        config = new LDAPSetupTask.Config("test.connection", LDAPValidatorTest.ldapTestUrl, LDAPValidatorTest.ldapAdminDn, LDAPValidatorTest.ldapAdminPassword);
        config.setRealmName(LDAP_REALM_NAME);
        config.setFilterType(LDAPSetupTask.FilterType.USERNAME);
        config.setFilter("uid");
        config.setRecursive(false);
        config.setBaseDN("o=TestOrganization");
        idata.putConfig(config);
    }

    @After
    public void tearDown() throws Exception {
        ldapServer.stopServer();
        testServer.restoreConfigs();
    }

    @Test
    public void testApplyToStandalone() throws Exception {
        try {
            standaloneServer.start("standalone.xml");
            assertTrue(new LDAPSetupTask().applyToStandalone(idata, standaloneServer, printer));
        } finally {
            standaloneServer.shutdown();
        }
        verifyCanLogIn(standaloneServer);
    }

    @Test
    public void applyToMultipleConfigTest() throws Exception {
        idata.putConfig(new CredentialStoreInstallTask.Config("test-store", null, TARGET_PATH.resolve("test.store").toString(), "abcd1234"));
        try {
            standaloneServer.start("standalone-full.xml");
            assertTrue(new CredentialStoreInstallTask().applyToStandalone(idata, standaloneServer, printer));
            assertTrue(new LDAPSetupTask().applyToStandalone(idata, standaloneServer, printer));
        } finally {
            standaloneServer.shutdown();
        }
        try {
            standaloneServer.start("standalone-ha.xml");
            assertTrue(new CredentialStoreInstallTask().applyToStandalone(idata, standaloneServer, printer));
            assertTrue(new LDAPSetupTask().applyToStandalone(idata, standaloneServer, printer));
        } finally {
            standaloneServer.shutdown();
        }
    }
    @Test
    public void testApplyToDomain() throws Exception {
        try {
            domainServer.start("host.xml");
            assertTrue(new LDAPSetupTask().applyToDomain(idata, domainServer, printer));
        } finally {
            domainServer.shutdown();
        }
        verifyCanLogIn(domainServer);
    }

    @Test
    public void testNotSerializingPassword() throws Exception {
        final InstallationDataSerializer serializer = new InstallationDataSerializer(new MockLanguageUtils(), new MockInputConsole());
        final Path outputFile = tempFolder.newFile().toPath();
        serializer.serialize(idata, outputFile);

        // we expect a password provided by the input console, due to missing .variables file and a missing value
        final LDAPSetupTask.Config config = serializer.deserialize(outputFile, Optional.empty()).getConfig(LDAPSetupTask.Config.class);
        assertEquals("test", config.getPassword());
    }

    @Test
    public void testSerializeConfig() throws Exception {
        final InstallationDataSerializer serializer = new InstallationDataSerializer(new MockLanguageUtils());
        final Path outputFile = tempFolder.newFile().toPath();
        serializer.serialize(idata, outputFile);

        final File variableFile = outputFile.getParent().resolve(outputFile.getFileName().toString() + ".variables").toFile();
        FileUtils.writeStringToFile(variableFile, LDAPSetupTask.Config.PASSWORD_PROPERTY_NAME + "=" + LDAPValidatorTest.ldapAdminPassword, "UTF-8");
        final LDAPSetupTask.Config deserializedConfig = serializer.deserialize(outputFile, Optional.empty()).getConfig(LDAPSetupTask.Config.class);

        assertEquals(idata.getConfig(LDAPSetupTask.Config.class), deserializedConfig);
    }

    @Test
    public void testSetRecurseSearch() throws Exception {
        config.setRecursive(true);
        try {
            standaloneServer.start("standalone.xml");
            assertTrue(new LDAPSetupTask().applyToStandalone(idata, standaloneServer, printer));
        } finally {
            standaloneServer.shutdown();
        }

        final StandaloneXmlUtil standaloneXml = new StandaloneXmlUtil(TARGET_PATH, "standalone.xml");
        final String xpath = String.format("//security-realms/ldap-realm[@name='%s']/identity-mapping", LDAP_REALM_NAME);
        final String isRecursive = standaloneXml.getAttribute(xpath, "use-recursive-search");
        assertEquals("Is recursive value should be set", isRecursive, "true");
    }

    public void verifyCanLogIn(EmbeddedServer embeddedServer) throws Exception {
        Process server = null;
        try {
            if (!embeddedServer.isDomain()) {
                server = ServerUtils.startServer(embeddedServer, "jbrown", "testpass");
            } else {
                // CLI is not using LDAP in domain mode
                server = ServerUtils.startServer(embeddedServer, "", "");
            }


            URL url = new URL("http://localhost:9990/management");
            String encoding = Base64.getEncoder().encodeToString(("jbrown:testpass").getBytes(StandardCharsets.UTF_8));

            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setDoOutput(true);
            connection.setRequestProperty("Authorization", "Basic " + encoding);
            assertEquals(200, connection.getResponseCode());
        } finally {
            if (server != null) {
                System.out.println("Shutting down ");
                ProcessHelper.destroyProcess(server);
                System.out.println("shut down");
            }
        }
    }

}