/**
 * 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.apache.commons.lang3.SystemUtils;
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 org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.AclEntry;
import java.nio.file.attribute.AclFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.security.Principal;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;
import static org.jboss.installer.postinstall.task.CredentialStoreInstallTask.KEY_CREDENTIAL_STORE_DIR;
import static org.jboss.installer.test.utils.TestServer.TARGET_PATH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class CredentialStoreInstallTaskTest {
    private XPath xPath =  XPathFactory.newInstance().newXPath();
    private DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    private DocumentBuilder builder;
    private DomainServer domainServer;
    private StandaloneServer standaloneServer;

    @ClassRule
    public static TestServer testServer = new TestServer();

    @Rule
    public TemporaryFolder temp = new TemporaryFolder();
    private CredentialStoreInstallTask task;
    private InstallationData idata;

    private TaskPrinter printer = new NoopPrinter();

    @Before
    public void setup() throws Exception {
        builder = factory.newDocumentBuilder();
        standaloneServer = new StandaloneServer(TARGET_PATH);
        domainServer = new DomainServer(TARGET_PATH);

        task = new CredentialStoreInstallTask();
        idata = new InstallationData();
        idata.setTargetFolder(TARGET_PATH);
        final CredentialStoreInstallTask.Config config = new CredentialStoreInstallTask.Config("test-keystore", null, TARGET_PATH.resolve("test.keystore").toString(), "abcd123");
        idata.putConfig(config);
    }

    @After
    public void tearDown() {
        standaloneServer.close();
        domainServer.close();
    }

    @Test
    public void testInstallCredentialStoreInStandalone() throws Exception {
        standaloneServer.start("standalone.xml");
        try {
            assertTrue(task.applyToStandalone(idata, standaloneServer, printer));

            Document doc = builder.parse(TARGET_PATH.resolve("standalone").resolve("configuration").resolve("standalone.xml").toFile());
            assertEquals("test-keystore", getAttribute(doc, "//credential-store", "name"));
            assertThat(getAttribute(doc, "//credential-store/credential-reference", "clear-text"))
                    .startsWith("${ENC");
            assertCredentialStoreFolderAccess(TARGET_PATH.resolve(StandaloneServer.CONFIG_DIR).resolve(KEY_CREDENTIAL_STORE_DIR));
        } finally {
            standaloneServer.shutdown();
        }
    }

    @Test
    public void testInstallCredentialStoreInDomain() throws Exception {
        domainServer.start("host.xml");
        try {
            assertTrue(task.applyToDomain(idata, domainServer, printer));

            Document domainDoc = builder.parse(TARGET_PATH.resolve("domain").resolve("configuration").resolve("domain.xml").toFile());
            assertEquals("test-keystore", getAttribute(domainDoc, "//credential-store", "name"));

            Document hostDoc = builder.parse(TARGET_PATH.resolve("domain").resolve("configuration").resolve("host.xml").toFile());
            assertEquals("test-keystore", getAttribute(hostDoc, "//credential-store", "name"));
            assertThat(getAttribute(hostDoc, "//credential-store/credential-reference", "clear-text"))
                    .startsWith("${ENC");
            assertCredentialStoreFolderAccess(TARGET_PATH.resolve(DomainServer.CONFIG_DIR).resolve(KEY_CREDENTIAL_STORE_DIR));
        } finally {
            domainServer.shutdown();
        }
    }

    @Test
    public void testInstallCredentialStoreInDomainSecondary() throws Exception {
        domainServer.start(DomainServer.HOST_SECONDARY_XML);
        try {

            assertTrue(task.applyToDomain(idata, domainServer, printer));

            Document hostDoc = builder.parse(TARGET_PATH.resolve("domain").resolve("configuration").resolve(DomainServer.HOST_SECONDARY_XML).toFile());
            assertEquals("test-keystore", getAttribute(hostDoc, "//credential-store", "name"));
        } finally {
            domainServer.shutdown();
        }
    }

    @Test
    public void testInstallCredentialStoreInDomainPrimary() throws Exception {
        domainServer.start(DomainServer.HOST_PRIMARY_XML);
        try {

            assertTrue(task.applyToDomain(idata, domainServer, printer));

            Document hostDoc = builder.parse(TARGET_PATH.resolve("domain").resolve("configuration").resolve(DomainServer.HOST_PRIMARY_XML).toFile());
            assertEquals("test-keystore", getAttribute(hostDoc, "//credential-store", "name"));
        } finally {
            domainServer.shutdown();
        }
    }

    private static void assertCredentialStoreFolderAccess(Path folder) throws Exception {
        if (SystemUtils.IS_OS_WINDOWS) {
            final AclFileAttributeView aclView = Files.getFileAttributeView(folder, AclFileAttributeView.class);
            final List<AclEntry> acl = aclView.getAcl();
            assertThat(acl)
                    .map(AclEntry::principal)
                    .map(Principal::getName)
                    .contains(aclView.getOwner().getName())
                    .isSubsetOf(aclView.getOwner().getName(), "BUILTIN\\Administrators", "NT AUTHORITY\\SYSTEM");
        } else {
            final Set<PosixFilePermission> posixFilePermissions = Files.getPosixFilePermissions(folder);
            assertThat(posixFilePermissions)
                    .containsOnly(PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_READ);
        }
    }

    @Test
    public void testSerializeConfig() throws Exception {
        final String aPassword = "another-password";
        final InstallationData idata = new InstallationData();
        final CredentialStoreInstallTask.Config config = new CredentialStoreInstallTask.Config("test-keystore", null, TARGET_PATH.resolve("test.keystore").toString(), "abcd123");
        idata.putConfig(config);

        final Path tempFile = temp.newFile("auto.xml").toPath();
        final InstallationDataSerializer serializer = new InstallationDataSerializer(new MockLanguageUtils());
        serializer.serialize(idata, tempFile);

        FileUtils.writeStringToFile(tempFile.getParent().resolve(tempFile.getFileName().toString() + ".variables").toFile(), "credential-store.password=" + aPassword, "UTF-8");
        final InstallationData loadedData = serializer.deserialize(tempFile, Optional.empty());

        final CredentialStoreInstallTask.Config loadedDataConfig = loadedData.getConfig(CredentialStoreInstallTask.Config.class);
        assertEquals("test-keystore", loadedDataConfig.getStoreName());
        assertEquals(aPassword, loadedDataConfig.getPassword());
    }

    private String getAttribute(Document doc, String expression, String attrName) throws XPathExpressionException {
        NodeList nodes = (NodeList) xPath.compile(expression).evaluate(
                doc, XPathConstants.NODESET);
        final Node attr = nodes.item(0).getAttributes().getNamedItem(attrName);
        return attr == null?null:attr.getNodeValue();
    }
}
