/**
 * 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.impl.secdom;

import static org.jboss.installer.postinstall.task.utils.ModelUtils.createEmptyOperation;
import static org.jboss.installer.postinstall.task.utils.ModelUtils.pathAddress;

import org.jboss.dmr.ModelNode;
import org.jboss.installer.postinstall.TaskPrinter;
import org.jboss.installer.postinstall.server.EmbeddedServer;
import org.jboss.installer.postinstall.server.ServerOperationException;
import org.jboss.installer.postinstall.task.CredentialStoreConfig;
import org.jboss.installer.postinstall.task.secdom.CertificateConfig;
import org.jboss.installer.postinstall.task.utils.CredentialStoreUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class CertSecurity {

    private final TaskPrinter printer;

    public CertSecurity(TaskPrinter printer) {
        this.printer = printer;
    }

    public List<ModelNode> toOperations(EmbeddedServer server, String domainName, CertificateConfig config, Optional<CredentialStoreConfig> credStoreConfig) throws ServerOperationException {
        final String trustStoreName = domainName + "-trust-store";
        final String trustManagerName = domainName + "-trust-manager";
        final String realmName = domainName + "-realm";
        final String authFactoryName = domainName + "-certHttpAuth";
        final String sslContextName = domainName + "-ssl-context";
        final String passwordAlias = domainName + "-password";

        final ArrayList<ModelNode> ops = new ArrayList<>();

        // set up trust store and manager
        final ModelNode addTrustStore = createEmptyOperation("add", pathAddress("subsystem", "elytron")
                .add("key-store", trustStoreName));

        try {
            if (credStoreConfig.isPresent() && !server.aliasExists(passwordAlias, credStoreConfig.get().getStoreName())) {
                printer.print("tasks.sec_dom.store_password", passwordAlias, credStoreConfig.get().getStoreName());
                server.encryptPassword(passwordAlias, credStoreConfig.get().getStoreName(), config.getTrustStorePassword());
            }
        } catch (ServerOperationException e) {
            printer.print("tasks.sec_dom.cred_store_access", credStoreConfig.get().getStoreName());
            throw e;
        }

        CredentialStoreUtil.addCredReference(addTrustStore, credStoreConfig, passwordAlias, config.getTrustStorePassword(), "credential-reference");
        server.relativise(config.getTrustStorePath(), addTrustStore);
        ops.add(addTrustStore);

        final ModelNode addTrustManager = createEmptyOperation("add", pathAddress("subsystem", "elytron")
                .add("trust-manager", trustManagerName));
        addTrustManager.get("key-store").set(trustStoreName);
        ops.add(addTrustManager);

        final ModelNode addKsRealm = createEmptyOperation("add", pathAddress("subsystem", "elytron")
                .add("key-store-realm", realmName));
        addKsRealm.get("key-store").set(trustStoreName);
        ops.add(addKsRealm);

        // map the Cert properties into a principal
        final ModelNode addAttrPrincipalDecoder = createEmptyOperation("add", pathAddress("subsystem", "elytron")
                .add("x500-attribute-principal-decoder", "CNDecoder"));
        if (config.isUseOid()) {
            addAttrPrincipalDecoder.get("oid").set(config.getFilterExpression());
        } else {
            addAttrPrincipalDecoder.get("attribute-name").set(config.getFilterExpression());
        }
        addAttrPrincipalDecoder.get("start-segment").set(config.getStartSegments());
        addAttrPrincipalDecoder.get("maximum-segments").set(config.getMaximumSegments());
        ops.add(addAttrPrincipalDecoder);

        // create a realm to assign fixed Roles
        final ModelNode addRoleMapper = createEmptyOperation("add", pathAddress("subsystem", "elytron")
                .add("constant-role-mapper", "constantClientCertRole"));
        for (String role: config.getRoles()) {
            addRoleMapper.get("roles").add(role);
        }
        ops.add(addRoleMapper);

        final ModelNode addSecurityDomain = createEmptyOperation("add", pathAddress("subsystem", "elytron")
                .add("security-domain", domainName));
        ModelNode realm = new ModelNode();
        realm.get("realm").set(realmName);
        addSecurityDomain.get("realms").add(realm);
        addSecurityDomain.get("default-realm").set(realmName);
        addSecurityDomain.get("permission-mapper").set("default-permission-mapper");
        addSecurityDomain.get("principal-decoder").set("CNDecoder");
        addSecurityDomain.get("role-mapper").set("constantClientCertRole");
        ops.add(addSecurityDomain);

        // create new sec application sec domain supporting CLIENT_CERT
        final ModelNode addHttpAuthFactory = createEmptyOperation("add", pathAddress("subsystem", "elytron")
                .add("http-authentication-factory", authFactoryName));
        addHttpAuthFactory.get("http-server-mechanism-factory").set("global");
        addHttpAuthFactory.get("security-domain").set(domainName);
        final ModelNode cfg = new ModelNode();
        cfg.get("mechanism-name").set("CLIENT_CERT");
        addHttpAuthFactory.get("mechanism-configurations").add(cfg);
        ops.add(addHttpAuthFactory);

        final ModelNode addAppDomain = createEmptyOperation("add", pathAddress("subsystem", "undertow")
                .add("application-security-domain", config.getApplicationDomainName()));
        addAppDomain.get("http-authentication-factory").set(authFactoryName);
        ops.add(addAppDomain);

        // create ssl-context to associate sec domain with key and trust managers and require client auth
        final ModelNode addSslContext = createEmptyOperation("add", pathAddress("subsystem", "elytron")
                .add("server-ssl-context", sslContextName));
        addSslContext.get("security-domain").set(domainName);
        addSslContext.get("key-manager").set("applicationKM");
        addSslContext.get("need-client-auth").set("true");
        addSslContext.get("trust-manager").set(trustManagerName);
        addSslContext.get("authentication-optional").set("true");
        ops.add(addSslContext);

        // set the new ssl context on https-listener
        final ModelNode writeSslContext = createEmptyOperation("write-attribute", pathAddress("subsystem", "undertow")
                .add("server", "default-server")
                .add("https-listener", "https"));
        writeSslContext.get("name").set("ssl-context");
        writeSslContext.get("value").set(sslContextName);
        ops.add(writeSslContext);

        return ops;
    }


}
