/*
 * Copyright 2022 Red Hat, Inc.
 *
 * 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.wildfly.test.manual.management;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import javax.inject.Inject;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.test.integration.domain.management.util.DomainTestUtils;
import org.jboss.as.test.integration.management.cli.CliProcessWrapper;
import org.jboss.as.test.shared.util.AssumeTestGroupUtil;
import org.jboss.dmr.ModelNode;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.wildfly.core.testrunner.ManagementClient;
import org.wildfly.core.testrunner.Server;
import org.wildfly.core.testrunner.ServerControl;
import org.wildfly.core.testrunner.ServerController;
import org.wildfly.core.testrunner.WildflyTestRunner;

/**
 * <p>Test that executes the <em>enable-elytron-se17.cli</em> script against
 * the default configurations and then checks that everything is in place.</p>
 *
 * @author rmartinc
 */
@RunWith(WildflyTestRunner.class)
@ServerControl(manual = true)
public class EnableElytronSE17TestCase {

    private static final Path JBOSS_DIST = Paths.get(System.getProperty("jboss.dist"));
    private static final Path JBOSS_HOME = Paths.get(System.getProperty("jboss.home"));
    private static final Path CONFIG_DIR_DIST = JBOSS_DIST.resolve("standalone").resolve("configuration");
    private static final Path CONFIG_DIR_HOME = JBOSS_HOME.resolve("standalone").resolve("configuration");
    private static final Path SCRIPT_FILE = JBOSS_HOME.resolve("docs").resolve("examples").resolve("enable-elytron-se17.cli");

    @Inject
    private ServerController serverController;

    @Before
    public void check() {
        // executing for all elytron enabled profiles not only in jdk-17
        Assume.assumeTrue(AssumeTestGroupUtil.isElytronProfileEnabled());
    }

    private void executeCli(Path configPath) throws IOException {
        CliProcessWrapper cli = new CliProcessWrapper()
                .addCliArgument("--file=" + SCRIPT_FILE.toAbsolutePath())
                .addJavaOption("-Dconfig=" + configPath.getFileName());
        String output = cli.executeNonInteractive();
        Assert.assertEquals("Script " + SCRIPT_FILE + " executed with errors: " + output, 0, cli.getProcessExitValue());
    }

    private boolean subsystemExists(ManagementClient client, String name) throws Exception {
        ModelNode op =  Util.createEmptyOperation(ModelDescriptionConstants.READ_RESOURCE_OPERATION,
                PathAddress.pathAddress(ModelDescriptionConstants.SUBSYSTEM, name));
        ModelNode res = client.getControllerClient().execute(op);
        return ModelDescriptionConstants.SUCCESS.equals(res.get(ModelDescriptionConstants.OUTCOME).asString());
    }

    private boolean extensionExists(ManagementClient client, String name) throws Exception {
        ModelNode op =  Util.createEmptyOperation(ModelDescriptionConstants.READ_RESOURCE_OPERATION,
                PathAddress.pathAddress(ModelDescriptionConstants.EXTENSION, name));
        ModelNode res = client.getControllerClient().execute(op);
        return ModelDescriptionConstants.SUCCESS.equals(res.get(ModelDescriptionConstants.OUTCOME).asString());
    }

    private void checkPathExists(ManagementClient client, PathAddress addr) throws Exception {
        ModelNode op =  Util.createEmptyOperation(ModelDescriptionConstants.READ_RESOURCE_OPERATION, addr);
        DomainTestUtils.executeForResult(op, client.getControllerClient());
    }

    private void checkPathNotExists(ManagementClient client, PathAddress addr) throws Exception {
        ModelNode op =  Util.createEmptyOperation(ModelDescriptionConstants.READ_RESOURCE_OPERATION, addr);
        DomainTestUtils.executeForFailure(op, client.getControllerClient());
    }

    private void checkAttributeDefined(ManagementClient client, PathAddress addr, String name) throws Exception {
        ModelNode op =  Util.createOperation(ModelDescriptionConstants.READ_ATTRIBUTE_OPERATION, addr);
        op.get(ModelDescriptionConstants.NAME).set(name);
        ModelNode res = DomainTestUtils.executeForResult(op, client.getControllerClient());
        Assert.assertTrue("Attribute " + name + " is not defined", res.isDefined());
    }

    private void checkNoChildren(ManagementClient client, PathAddress addr, String type) throws Exception {
        ModelNode op =  Util.createOperation(ModelDescriptionConstants.READ_CHILDREN_NAMES_OPERATION, addr);
        op.get(ModelDescriptionConstants.CHILD_TYPE).set(type);
        ModelNode res = DomainTestUtils.executeForResult(op, client.getControllerClient());
        Assert.assertTrue("Child " + type + " is not empty", res.asList().isEmpty());
    }

    private void checkNoBootErrors(ManagementClient client) throws Exception {
        ModelNode op = Util.createEmptyOperation("read-boot-errors", PathAddress.pathAddress(
                ModelDescriptionConstants.CORE_SERVICE, ModelDescriptionConstants.MANAGEMENT));
        ModelNode res = DomainTestUtils.executeForResult(op, client.getControllerClient());
        Assert.assertTrue("Errors at boot", res.asList().isEmpty());
    }

    private void checkServer(String configFile) throws Exception {
        ManagementClient client = serverController.getClient();

        // server started without boot errors
        checkNoBootErrors(client);

        // check elytron configuration is in place
        // /subsystem=elytron/http-authentication-factory=application-http-authentication
        checkPathExists(client, PathAddress.pathAddress(ModelDescriptionConstants.SUBSYSTEM, "elytron")
                .append("http-authentication-factory", "application-http-authentication"));
        // /subsystem=undertow/application-security-domain=other
        checkPathExists(client, PathAddress.pathAddress(ModelDescriptionConstants.SUBSYSTEM, "undertow")
                .append("application-security-domain", "other"));
        // /subsystem=undertow/server=default-server/https-listener=https:read-attribute(name=ssl-context)
        checkAttributeDefined(client, PathAddress.pathAddress(ModelDescriptionConstants.SUBSYSTEM, "undertow")
                .append("server", "default-server").append("https-listener", "https"), "ssl-context");
        // /subsystem=undertow/server=default-server/host=default-host/setting=http-invoker:read-attribute(name=http-authentication-factory)
        checkAttributeDefined(client, PathAddress.pathAddress(ModelDescriptionConstants.SUBSYSTEM, "undertow")
                        .append("server", "default-server").append("host", "default-host").append("setting", "http-invoker"),
                "http-authentication-factory");
        // /subsystem=ejb3/application-security-domain=other
        checkPathExists(client, PathAddress.pathAddress(ModelDescriptionConstants.SUBSYSTEM, "ejb3")
                .append("application-security-domain", "other"));
        // /subsystem=batch-jberet:read-attribute(name=security-domain)
        checkAttributeDefined(client, PathAddress.pathAddress(ModelDescriptionConstants.SUBSYSTEM, "batch-jberet"),
                "security-domain");
        // activemq subsystem is not present in all configurations
        if (subsystemExists(client, "messaging-activemq")) {
            // /subsystem=messaging-activemq/server=default:read-attribute(name=elytron-domain)
            checkAttributeDefined(client, PathAddress.pathAddress(ModelDescriptionConstants.SUBSYSTEM, "messaging-activemq")
                    .append("server", "default"), "elytron-domain");
        }
        // /subsystem=remoting/http-connector=http-remoting-connector:read-attribute(name=sasl-authentication-factory)
        checkAttributeDefined(client, PathAddress.pathAddress(ModelDescriptionConstants.SUBSYSTEM, "remoting")
                .append("http-connector", "http-remoting-connector"), "sasl-authentication-factory");

        // https://issues.redhat.com/browse/JBEAP-24741
        if ((configFile != "standalone.xml") && (configFile != "standalone-ha.xml")) {
            // /subsystem=iiop-openjdk:read-attribute(name=security)
            checkAttributeDefined(client, PathAddress.pathAddress(ModelDescriptionConstants.SUBSYSTEM, "iiop-openjdk"), "security");
        }

        // /core-service=management:read-children-names(child-type=security-realm)
        checkNoChildren(client, PathAddress.pathAddress(ModelDescriptionConstants.CORE_SERVICE, "management"), "security-realm");
        // /core-service=vault:read-resource
        checkPathNotExists(client, PathAddress.pathAddress(ModelDescriptionConstants.CORE_SERVICE, "vault"));
        // subsystems and extensions removed
        Assert.assertFalse("Subsystem security is not removed", subsystemExists(client, "security"));
        Assert.assertFalse("Subsystem picketlink-federation is not removed", subsystemExists(client, "picketlink-federation"));
        Assert.assertFalse("Subsystem picketlink-identity-management is not removed", subsystemExists(client, "picketlink-identity-management"));
        Assert.assertFalse("Extension org.jboss.as.security is not removed", extensionExists(client, "org.jboss.as.security"));
        Assert.assertFalse("Extension org.wildfly.extension.picketlink is not removed", extensionExists(client, "org.wildfly.extension.picketlink"));
    }

    private void doTest(String configFile) throws Exception {
        Path configPath = CONFIG_DIR_DIST.resolve(configFile);
        Assert.assertTrue("File " + configPath.getFileName() + " is not readable", Files.isReadable(configPath));

        // work using a copy of the passed file and delete after the test
        Path workingPath = CONFIG_DIR_HOME.resolve("SE17copy-" + configPath.getFileName());
        Files.copy(configPath, workingPath, StandardCopyOption.REPLACE_EXISTING);

        try {
            executeCli(workingPath);
            serverController.start(workingPath.getFileName().toString(), Server.StartMode.NORMAL);
            checkServer(configFile);
        } finally {
            if (serverController != null && serverController.isStarted()) {
                serverController.stop();
            }
            Files.deleteIfExists(workingPath);
        }
    }

    @Test
    public void testStandalone() throws Exception {
        doTest("standalone.xml");
    }

    @Test
    public void testStandaloneHa() throws Exception {
        doTest("standalone-ha.xml");
    }

    @Test
    public void testStandaloneFull() throws Exception {
        doTest("standalone-full.xml");
    }

    @Test
    public void testStandaloneFullHa() throws Exception {
        doTest("standalone-full-ha.xml");
    }
}
