/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2023 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.jboss.installer.postinstall.task;

import com.google.auto.service.AutoService;
import org.apache.commons.lang3.tuple.Pair;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathAddress;
import org.jboss.dmr.ModelNode;
import org.jboss.installer.core.InstallationData;
import org.jboss.installer.postinstall.PostInstallTask;
import org.jboss.installer.postinstall.TaskPrinter;
import org.jboss.installer.postinstall.server.DomainServer;
import org.jboss.installer.postinstall.server.StandaloneServer;

import java.util.List;
import java.util.Set;

import static org.jboss.as.controller.operations.common.Util.createEmptyOperation;
import static org.jboss.installer.core.LoggerUtils.taskLog;

@AutoService(PostInstallTask.class)
public class HttpsApplicationEnableTask extends AbstractHttpsEnableTask {

    private static final String NAME_KEY = "postinstall.task.enable_application_ssl.name";

    public static final String DEFAULT_KEYSTORE_FILE_NAME = "application.keystore";
    public static final String DEFAULT_TRUSTSTORE_FILE_NAME = "application.truststore";
    public static final String KEYSTORE_NAME = "application-keystore";
    public static final String TRUSTSTORE_NAME = "application-truststore";
    public static final String KEYMANAGER_NAME = "application-keymanager";
    public static final String TRUSTMANAGER_NAME = "application-trustmanager";
    public static final String SSL_CONTEXT_NAME = "application-ssl-context";
    public static final String CREDENTIAL_ALIAS_KEYSTORE_PASSWORD = "ssl-application-keystore-password";
    public static final String CREDENTIAL_ALIAS_TRUSTSTORE_PASSWORD = "ssl-application-truststore-password";

    @Override
    public String getName() {
        return NAME_KEY;
    }

    @Override
    public String getSerializationName() {
        return "enable-application-https";
    }

    @Override
    public Class<? extends InstallationData.PostInstallConfig> getConfigClass() {
        return Config.class;
    }

    public static class Config extends AbstractHttpsEnableTask.Config {
        public Config() {
            // no-op for unmarshalling
        }

        public Config(Set<String> protocols, String cipherSuites, String tls13cipherNames, String keyStorePath,
                      String keyStorePassword, boolean mutual, String trustStorePath, String trustStorePassword,
                      boolean doGenerateStore, String dn, String validity, String clientCertPath, boolean validateCertificate) {
            super(protocols, cipherSuites, tls13cipherNames, keyStorePath, keyStorePassword, mutual, trustStorePath, trustStorePassword,
                    doGenerateStore, dn, validity, clientCertPath, validateCertificate);
        }
    }

    public boolean applyToStandalone(InstallationData data, StandaloneServer server, TaskPrinter printer) {
        try {
            printer.print("tasks.application_ssl.started", server.currentConfiguration());
            // create key store, trust store, key manager, trust manager, ssl context
            final List<Pair<ModelNode, String>> ops = getCommonOperations(data, server, SSL_CONTEXT_NAME,
                    CREDENTIAL_ALIAS_KEYSTORE_PASSWORD, CREDENTIAL_ALIAS_TRUSTSTORE_PASSWORD,
                    DEFAULT_KEYSTORE_FILE_NAME, KEYSTORE_NAME, KEYMANAGER_NAME,
                    DEFAULT_TRUSTSTORE_FILE_NAME, TRUSTSTORE_NAME, TRUSTMANAGER_NAME, printer);

            ops.add(Pair.of(undefineLegacySecurityRealm(), "Undefine legacy security realm"));
            ops.add(Pair.of(setSslContextOnHttpsListener(SSL_CONTEXT_NAME), "Set ssl-context on https listener"));

            for (Pair<ModelNode, String> opPair : ops) {
                server.execute(opPair.getLeft(), opPair.getRight());
            }
        } catch (OperationFailedException e) {
            taskLog.error("CLI operation failed", e);
            printer.print("tasks.application_ssl.failed");
            printer.print(e);
            return false;
        }
        printer.print("tasks.application_ssl.finished");
        return true;
    }

    @Override
    public boolean applyToDomain(InstallationData data, DomainServer server, TaskPrinter printer) {
        try {
            printer.print("tasks.application_ssl.started", server.currentConfiguration());
            // the following has to be run only once
            if (server.currentConfiguration().equals("host.xml")) {
                // create key store, trust store, key manager, trust manager, ssl context
                final List<Pair<ModelNode, String>> ops = getCommonOperations(data, server, SSL_CONTEXT_NAME,
                        CREDENTIAL_ALIAS_KEYSTORE_PASSWORD, CREDENTIAL_ALIAS_TRUSTSTORE_PASSWORD,
                        DEFAULT_KEYSTORE_FILE_NAME, KEYSTORE_NAME, KEYMANAGER_NAME,
                        DEFAULT_TRUSTSTORE_FILE_NAME, TRUSTSTORE_NAME, TRUSTMANAGER_NAME, false, printer);

                ops.add(Pair.of(undefineLegacySecurityRealm(), "Undefine legacy security realm"));
                ops.add(Pair.of(setSslContextOnHttpsListener(SSL_CONTEXT_NAME), "Set ssl-context on https listener"));

                for (Pair<ModelNode, String> opPair : ops) {
                    server.executeOnProfiles(opPair.getLeft(), opPair.getRight());
                }
            }
        } catch (OperationFailedException e) {
            taskLog.error("CLI operation failed", e);
            printer.print("tasks.application_ssl.failed");
            printer.print(e);
            return false;
        }
        printer.print("tasks.application_ssl.finished");
        return true;
    }

    private ModelNode setSslContextOnHttpsListener(String sslContextName) {
        // /subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=ssl-context,value=demoSSLContext)
        final ModelNode writeOp = createEmptyOperation("write-attribute",
                PathAddress.pathAddress("subsystem", "undertow").append("server", "default-server").append("https-listener", "https"));
        writeOp.get("name").set("ssl-context");
        writeOp.get("value").set(sslContextName);
        return writeOp;
    }

    private ModelNode undefineLegacySecurityRealm() {
        // /subsystem=undertow/server=default-server/https-listener=https:undefine-attribute(name=security-realm)
        final ModelNode writeOp = createEmptyOperation("undefine-attribute",
                PathAddress.pathAddress("subsystem", "undertow").append("server", "default-server").append("https-listener", "https"));
        writeOp.get("name").set("security-realm");
        return writeOp;
    }
}
