/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2024 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.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.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.jboss.dmr.ModelNode;
import org.jboss.installer.core.DatabaseDriver;
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.EmbeddedServer;
import org.jboss.installer.postinstall.server.ServerOperationException;
import org.jboss.installer.postinstall.server.StandaloneServer;
import org.jboss.installer.postinstall.task.JDBCDriverConfig;
import org.jboss.installer.postinstall.task.CredentialStoreConfig;
import org.jboss.installer.postinstall.task.DatasourceConfig;
import org.jboss.installer.postinstall.task.utils.CredentialStoreUtil;

class DatasourceTask implements CliPostInstallTaskImpl {
    protected static final String XA_RECOVERY_PASSWORD_ALIAS = "xa-recovery-password";
    protected static final String DATASOURCE_PASSWORD_ALIAS = "datasource-password";
    public static final String EXCEPTION_SORTER = "exception-sorter-class-name";
    public static final String VALID_CONNECTION_CHECKER = "valid-connection-checker-class-name";
    public static final String STALE_CONNECTION_CHECKER = "stale-connection-checker-class-name";

    @Override
    public boolean applyToStandalone(InstallationData data, StandaloneServer server, TaskPrinter printer) {
        return doApply(data, server, printer);
    }

    @Override
    public boolean applyToDomain(InstallationData data, DomainServer server, TaskPrinter printer) {
        if (!server.currentConfiguration().equals("host.xml")) {
            return true;
        }
        return doApply(data, server, printer);
    }

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

    private boolean doApply(InstallationData data, EmbeddedServer server, TaskPrinter printer) {
        final DatasourceConfig datasourceConfig = data.getConfig(DatasourceConfig.class);
        final JDBCDriverConfig jdbcConfig = data.getConfig(JDBCDriverConfig.class);
        printer.print("tasks.datasource.started", datasourceConfig.getDsName(), server.currentConfiguration());

        final Optional<CredentialStoreConfig> storeConfig = Optional.ofNullable(data.getConfig(CredentialStoreConfig.class));
        taskLog.info(String.format("Starting applying datasource changes to %s", server.currentConfiguration()));
        try {
            taskLog.debug("Applying Datasource change");

            if (storeConfig.isPresent() && !server.aliasExists(DATASOURCE_PASSWORD_ALIAS, storeConfig.get().getStoreName())) {
                if (datasourceConfig.getDsPassword() != null) {
                    printer.print("tasks.datasource.store_password", storeConfig.get().getStoreName(), DATASOURCE_PASSWORD_ALIAS);
                    server.encryptPassword(DATASOURCE_PASSWORD_ALIAS, storeConfig.get().getStoreName(), datasourceConfig.getDsPassword());
                }
                if (datasourceConfig.getXaPassword() != null) {
                    printer.print("tasks.datasource.store_password", storeConfig.get().getStoreName(), XA_RECOVERY_PASSWORD_ALIAS);
                    server.encryptPassword(XA_RECOVERY_PASSWORD_ALIAS, storeConfig.get().getStoreName(), datasourceConfig.getXaPassword());
                }
            }

            List<ModelNode> operations = new ArrayList<>();
            operations.add(getDatasourceOpNode(datasourceConfig, jdbcConfig.getDatabaseDriver(), storeConfig));
            operations.addAll(addXaProperties(datasourceConfig));
            for (int i = 0; i < operations.size(); i++) {
                ModelNode operation = operations.get(i);
                if (server.isDomain()) {
                    ((DomainServer) server).executeOnProfiles(operation, "Setting datasource - step " + i);
                } else {
                    server.execute(operation, "Setting datasource - step " + i);
                }
            }

        } catch (ServerOperationException e) {
            taskLog.error("CLI operation failed", e);
            printer.print("tasks.datasource.failed");
            printer.print(e);
            return false;
        }
        printer.print("tasks.datasource.finished");
        return true;
    }

    private ModelNode getDatasourceOpNode(DatasourceConfig config, DatabaseDriver driver, Optional<CredentialStoreConfig> storeConfig) {
        boolean isXa = config.getXaProperties() != null;
        final ModelNode modelNode;
        if (isXa) {
            modelNode = createEmptyOperation("add",
                    pathAddress("subsystem", "datasources").add("xa-data-source", config.getDsName()));
            modelNode.get("recovery-username").set(config.getXaUsername());
            CredentialStoreUtil.addCredReference(modelNode, storeConfig, XA_RECOVERY_PASSWORD_ALIAS, config.getXaPassword(), "recovery-credential-reference");
        } else {
            modelNode = createEmptyOperation("add",
                    pathAddress("subsystem", "datasources").add("data-source", config.getDsName()));
            modelNode.get("connection-url").set(config.getConnectionUrl());
        }

        if (config.getSecurityDomain() == null) {
            modelNode.get("user-name").set(config.getDsUsername());
            CredentialStoreUtil.addCredReference(modelNode, storeConfig, DATASOURCE_PASSWORD_ALIAS, config.getDsPassword(), "credential-reference");
        } else {
            modelNode.get("security-domain").set(config.getSecurityDomain());
        }

        modelNode.get("driver-name").set(driver.getJdbcName());
        modelNode.get("jndi-name").set(config.getJndiName());
        modelNode.get("min-pool-size").set(config.getMinPool());
        modelNode.get("max-pool-size").set(config.getMaxPool());

        setUniqueDatasourceElements(modelNode, driver);
        return modelNode;
    }

    public static void setUniqueDatasourceElements(ModelNode modelNode, DatabaseDriver driver) {
        switch (driver) {
            case IBM_DB2:
                modelNode.get(EXCEPTION_SORTER).set("org.jboss.jca.adapters.jdbc.extensions.db2.DB2ExceptionSorter");
                modelNode.get(VALID_CONNECTION_CHECKER).set("org.jboss.jca.adapters.jdbc.extensions.db2.DB2ValidConnectionChecker");
                modelNode.get(STALE_CONNECTION_CHECKER).set("org.jboss.jca.adapters.jdbc.extensions.db2.DB2StaleConnectionChecker");
                break;
            case SYBASE_JCONN:
                modelNode.get(EXCEPTION_SORTER).set("org.jboss.jca.adapters.jdbc.extensions.sybase.SybaseExceptionSorter");
                modelNode.get(VALID_CONNECTION_CHECKER).set("org.jboss.jca.adapters.jdbc.extensions.sybase.SybaseValidConnectionChecker");
                break;
            case MYSQL:
            case MARIA_DB:
                modelNode.get(EXCEPTION_SORTER).set("org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter");
                modelNode.get(VALID_CONNECTION_CHECKER).set("org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker");
                break;
            case ENTERPRISE_DB:
            case POSTGRE_SQL:
                modelNode.get(EXCEPTION_SORTER).set("org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter");
                modelNode.get(VALID_CONNECTION_CHECKER).set("org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker");
                break;
            case MICROSOFT_SQL_SERVER:
                modelNode.get(VALID_CONNECTION_CHECKER).set("org.jboss.jca.adapters.jdbc.extensions.mssql.MSSQLValidConnectionChecker");
                break;
            case ORACLE:
                modelNode.get(EXCEPTION_SORTER).set("org.jboss.jca.adapters.jdbc.extensions.oracle.OracleExceptionSorter");
                modelNode.get(VALID_CONNECTION_CHECKER).set("org.jboss.jca.adapters.jdbc.extensions.oracle.OracleValidConnectionChecker");
                modelNode.get(STALE_CONNECTION_CHECKER).set("org.jboss.jca.adapters.jdbc.extensions.oracle.OracleStaleConnectionChecker");
                break;
        }
    }

    private List<ModelNode> addXaProperties(DatasourceConfig config) {
        List<ModelNode> operations = new ArrayList<>();
        if (config.getXaProperties() == null) {
            return operations;
        }
        for (DatasourceConfig.XaProperty property : config.getXaProperties()) {
            ModelNode addPropertyOp = createEmptyOperation("add",
                    pathAddress("subsystem", "datasources").add("xa-data-source", config.getDsName()).add("xa-datasource-properties", property.getKey()));
            addPropertyOp.get("value").set(property.getValue());
            operations.add(addPropertyOp);
        }
        return operations;
    }
}
