package org.jboss.installer.postinstall.task;

import com.google.auto.service.AutoService;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathAddress;
import org.jboss.dmr.ModelNode;
import org.jboss.installer.auto.AutomaticInstallationParsingException;
import org.jboss.installer.core.DatabaseDriver;
import org.jboss.installer.core.FlatListPostInstallConfig;
import org.jboss.installer.core.InstallationData;
import org.jboss.installer.postinstall.CliPostInstallTask;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

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

@AutoService(PostInstallTask.class)
public class JDBCDriverTask implements CliPostInstallTask {

    public static final String TASK_NAME = "post_install.task.jdbc_driver_configuration.name";

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

    @Override
    public String getSerializationName() {
        return "add-driver";
    }

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

    @Override
    public boolean applyToStandalone(InstallationData data, StandaloneServer server, TaskPrinter printer) {
        taskLog.info(String.format("Starting applying JDBC driver changes to %s", server.currentConfiguration()));
        printer.print("tasks.jdbc_driver.started", server.currentConfiguration());

        final Config config = data.getConfig(Config.class);
        assert config != null;
        try {
            taskLog.debug("Applying JDBC driver change");
            final ModelNode jdbcDriverOp = getJdbcDriverOp(config);
            server.execute(jdbcDriverOp, "Set JDBC Driver");
        } catch (OperationFailedException e) {
            taskLog.error("CLI operation failed", e);
            printer.print("tasks.jdbc_driver.failed");
            printer.print(e);
            return false;
        }
        printer.print("tasks.jdbc_driver.finished");
        return true;
    }

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

        final Config config = data.getConfig(Config.class);
        assert config != null;

        taskLog.info(String.format("Starting applying JDBC driver changes to %s", server.currentConfiguration()));
        printer.print("tasks.jdbc_driver.started", server.currentConfiguration());
        try {
            taskLog.debug("Applying JDBC driver change");
            final ModelNode jdbcDriverOp = getJdbcDriverOp(config);
            server.executeOnProfiles(jdbcDriverOp, "Set JDBC Driver");

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

    private ModelNode getJdbcDriverOp(Config config) {
        final ModelNode driverOp = createEmptyOperation("add",
                PathAddress.pathAddress("subsystem", "datasources").append("jdbc-driver", config.getDatabaseDriver().getJdbcName()));
        driverOp.get("driver-name").set(config.getDatabaseDriver().getJdbcName());
        driverOp.get("driver-module-name").set(config.getDatabaseDriver().getModuleName());
        driverOp.get("driver-xa-datasource-class-name").set(config.getDatabaseDriver().getXaClassName());
        return driverOp;
    }

    public static class Config extends FlatListPostInstallConfig {

        private DatabaseDriver databaseDriver;
        private List<String> jarList;
        // not serialized
        private List<String> resolvedJarList;

        public Config(){

        }

        public Config(DatabaseDriver databaseDriver, List<String> jarList, List<String> resolvedJarLocations) {
            this.databaseDriver = databaseDriver;
            this.jarList = jarList;
            this.resolvedJarList = resolvedJarLocations;
        }

        public DatabaseDriver getDatabaseDriver() {
            return databaseDriver;
        }

        public List<String> getJarList() {
            return jarList;
        }

        public List<String> getResolvedJarList() {
            return resolvedJarList;
        }

        @Override
        protected Map<String, String> listAttributes() {
            HashMap<String, String> attributes = new HashMap<>();
            attributes.put("databaseDriver", databaseDriver.name());
            for (int i = 0; i < jarList.size(); i++) {
                attributes.put("jarPath-" + i, jarList.get(i));
            }
            return attributes;
        }

        @Override
        protected void acceptAttributes(Map<String, String> attributes, BiFunction<String, String, String> variableResolver) throws AutomaticInstallationParsingException {
            databaseDriver = DatabaseDriver.valueOf(attributes.get("databaseDriver"));
            jarList = getListOfJarPaths(attributes);

        }

        @Override
        public int hashCode() {
            return Objects.hash(databaseDriver, jarList);
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            JDBCDriverTask.Config config = (JDBCDriverTask.Config) obj;
            return Objects.equals(databaseDriver, config.databaseDriver) && Objects.equals(jarList, config.jarList);
        }

        private List<String> getListOfJarPaths(Map<String, String> attributes) {
            return attributes.entrySet().stream()
                    .filter(entry -> entry.getKey().startsWith("jarPath"))
                    .map(Map.Entry::getValue)
                    .collect(Collectors.toList());
        }

        public boolean hasResolvedJarList() {
            return resolvedJarList != null && !resolvedJarList.isEmpty();
        }

        public void setResolvedJarList(List<String> resolvedJarList) {
            this.resolvedJarList = resolvedJarList;
        }
    }
}
