package org.jboss.installer.postinstall.task.impl;

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

import java.io.IOException;
import java.nio.file.Path;

import org.jboss.dmr.ModelNode;
import org.jboss.installer.core.InstallationData;
import org.jboss.installer.postinstall.CliPostInstallTaskImpl;
import org.jboss.installer.postinstall.TaskPrinter;
import org.jboss.installer.postinstall.server.DomainServer;
import org.jboss.installer.postinstall.server.ServerOperationException;
import org.jboss.installer.postinstall.server.StandaloneServer;
import org.jboss.installer.postinstall.task.CredentialStoreConfig;
import org.jboss.installer.postinstall.task.utils.FilePermissionUtils;

class CredentialStoreInstallTask implements CliPostInstallTaskImpl {

    protected static final String KEY_CREDENTIAL_STORE_DIR = "secret-key-store";
    private static final String SECRET_KEY_STORE_RESOLVER = "secret-key-store-resolver";
    protected static final String KEY_CREDENTIAL_STORE_NAME = "secret-key-credential-store";

    @Override
    public String getName() {
        return "credential_store.task.name";
    }

    @Override
    public boolean applyToStandalone(InstallationData data, StandaloneServer server, TaskPrinter printer) {
        printer.print("tasks.add_cred_store.started", server.currentConfiguration());
        final CredentialStoreConfig config = data.getConfig(CredentialStoreConfig.class);
        final ModelNode initialStoreOp = addSecretKeyStoreOp(data.getTargetFolder(), false);
        final ModelNode resolverOp = addSecretKeyStoreResolverOp();

        final String encryptedPassword;
        try {
            server.execute(initialStoreOp, "Install secret key credential store");
            server.execute(resolverOp, "Install secret key resolver");
            final ModelNode res = server.execute(encryptPasswordOp(config.getPassword()), "Encrypt password");
            encryptedPassword = res.get("expression").asString();
        } catch (ServerOperationException e) {
            taskLog.error("CLI operation failed", e);
            printer.print("tasks.add_cred_store.failed");
            printer.print(e);
            return false;
        }

        final ModelNode addKsOp = addCredentialStoreOp(config, encryptedPassword);
        try {
            server.execute(addKsOp, "Install keystore");
        } catch (ServerOperationException e) {
            taskLog.error("CLI operation failed", e);
            printer.print("tasks.add_cred_store.failed");
            printer.print(e);
            return false;
        }

        final Path initialKeyStore = data.getTargetFolder().resolve(StandaloneServer.CONFIG_DIR).resolve(KEY_CREDENTIAL_STORE_DIR);
        try {
            FilePermissionUtils.setOwnerOnly(initialKeyStore);
        } catch (IOException | FilePermissionUtils.UnsupportedFileSystemException e) {
            taskLog.warn("Failed to limit access to credential store + " + initialKeyStore, e);
            printer.print("tasks.permission_cred_store.failed", initialKeyStore.toString());
            // complete the task, but log a warning
            return true;
        }

        return true;
    }

    @Override
    public boolean applyToDomain(InstallationData data, DomainServer server, TaskPrinter printer) {
        printer.print("tasks.add_cred_store.started", server.currentConfiguration());
        final CredentialStoreConfig config = data.getConfig(CredentialStoreConfig.class);

        final ModelNode initialStoreOp = addSecretKeyStoreOp(data.getTargetFolder(), true);
        final ModelNode resolverOp = addSecretKeyStoreResolverOp();

        final String encryptedPassword;
        try {
            if (server.currentConfiguration().equals("host.xml")) {
                server.executeOnProfiles(initialStoreOp, "Install secret key credential store");
                server.executeOnProfiles(resolverOp, "Install secret key resolver");
            }

            server.execute(server.wrapInHost(initialStoreOp), "Install secret key credential store");
            server.execute(server.wrapInHost(resolverOp), "Install secret key resolver");
            final ModelNode res = server.execute(server.wrapInHost(encryptPasswordOp(config.getPassword())), "Encrypt password");
            encryptedPassword = res.get("expression").asString();
        } catch (ServerOperationException e) {
            taskLog.error("CLI operation failed", e);
            printer.print("tasks.add_cred_store.failed");
            printer.print(e);
            return false;
        }

        final ModelNode addKsOp = addCredentialStoreOp(config, encryptedPassword);
        try {
            // only execute this part once
            if (server.currentConfiguration().equals("host.xml")) {
                server.executeOnProfiles(addKsOp, "Install keystore on domain");
            }
            server.execute(server.wrapInHost(addKsOp), "Install keystore on host");
        } catch (ServerOperationException e) {
            taskLog.error("CLI operation failed", e);
            printer.print("tasks.add_cred_store.failed");
            printer.print(e);
            return false;
        }

        final Path initialKeyStore = data.getTargetFolder().resolve(DomainServer.CONFIG_DIR).resolve(KEY_CREDENTIAL_STORE_DIR);
        try {
            FilePermissionUtils.setOwnerOnly(initialKeyStore);
        } catch (IOException | FilePermissionUtils.UnsupportedFileSystemException e) {
            taskLog.warn("Failed to limit access to credential store + " + initialKeyStore, e);
            printer.print("tasks.permission_cred_store.failed", initialKeyStore.toString());
            // complete the task, but log a warning
            return true;
        }

        printer.print("tasks.add_cred_store.finished", config.getPath());
        return true;
    }

    private ModelNode addSecretKeyStoreOp(Path baseFolder, boolean domain) {
        final ModelNode addKsOp = createEmptyOperation("add",
                pathAddress("subsystem", "elytron").add("secret-key-credential-store", KEY_CREDENTIAL_STORE_NAME));
        addKsOp.get("path").set(Path.of(KEY_CREDENTIAL_STORE_DIR, "properties-store.cs").toString());
        addKsOp.get("relative-to").set(domain?"jboss.domain.config.dir":"jboss.server.config.dir");
        return addKsOp;
    }

    private ModelNode addSecretKeyStoreResolverOp() {
        final ModelNode addKsOp = createEmptyOperation("add",
                pathAddress("subsystem", "elytron").add("expression", "encryption"));
        final ModelNode resolver = new ModelNode();
        resolver.get("name").set(SECRET_KEY_STORE_RESOLVER);
        resolver.get("credential-store").set(KEY_CREDENTIAL_STORE_NAME);
        resolver.get("secret-key").set("key");
        addKsOp.get("resolvers").add(resolver);
        return addKsOp;
    }

    private ModelNode encryptPasswordOp(String password) {
        final ModelNode addKsOp = createEmptyOperation("create-expression",
                pathAddress("subsystem", "elytron").add("expression", "encryption"));
        addKsOp.get("resolver").set(SECRET_KEY_STORE_RESOLVER);
        addKsOp.get("clear-text").set(password);
        return addKsOp;
    }

    private ModelNode addCredentialStoreOp(CredentialStoreConfig config, String encryptedPassword) {
        final ModelNode addKsOp = createEmptyOperation("add",
                pathAddress("subsystem", "elytron").add("credential-store", config.getStoreName()));
        addKsOp.get("path").set(config.getPath());
        final ModelNode modelNode = new ModelNode();
        modelNode.get("clear-text").set(encryptedPassword);
        addKsOp.get("credential-reference").set(modelNode);
        addKsOp.get("create").set(true);
        if (config.getRelativeTo() != null) {
            addKsOp.get("relative-to").set(config.getRelativeTo());
        }
        return addKsOp;
    }
}
