/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2021 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 java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;

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.auto.InstallationDataSerializer;
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 static org.jboss.as.controller.operations.common.Util.createEmptyOperation;
import static org.jboss.installer.core.LoggerUtils.taskLog;

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

    private static final String TASK_NAME = "post_install.task.logging.name";

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

    @Override
    public String getSerializationName() {
        return "change-logging-level";
    }

    @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 logger level changes to %s", server.currentConfiguration()));
        printer.print("tasks.logger_levels.started", server.currentConfiguration());
        final Config config = data.getConfig(Config.class);
        assert config != null;

        try {
            taskLog.debug("Applying root logger change");
            final ModelNode rootLoggerOp = getLoggerLevelOp("root-logger", "ROOT", config.rootLevel);
            server.execute(rootLoggerOp, "Set root logger level");

            if (!server.currentConfiguration().equals("standalone-full-ha.xml")) {
                taskLog.debug("Applying console handler change");
                final ModelNode consoleLoggerOp = getLoggerLevelOp("console-handler", "CONSOLE", config.consoleLevel);
                server.execute(consoleLoggerOp, "Set console logger level");
            }
            taskLog.info(String.format("Finished applying logger level changes to %s", server.currentConfiguration()));
            printer.print("tasks.logger_levels.finished", server.currentConfiguration());
            return true;
        }catch (OperationFailedException e) {
            taskLog.error("CLI operation failed", e);
            printer.print("tasks.logger_levels.failed", server.currentConfiguration());
            printer.print(e);
            return false;
        }
    }

    @Override
    public boolean applyToDomain(InstallationData data, DomainServer server, TaskPrinter printer) {
        taskLog.info(String.format("Starting applying logger level changes to %s", server.currentConfiguration()));
        printer.print("tasks.logger_levels.started", server.currentConfiguration());
        final Config config = data.getConfig(Config.class);
        assert config != null;

        try {
            if (!server.currentConfiguration().equals(DomainServer.HOST_SECONDARY_XML)) {
                taskLog.debug("Applying root logger change");
                final ModelNode rootLoggerOp = getLoggerLevelOp("root-logger", "ROOT", config.rootLevel);
                server.executeOnProfiles(rootLoggerOp, "Set root logger level");
            }

            taskLog.info(String.format("Finished applying logger level changes to %s", server.currentConfiguration()));
            printer.print("tasks.logger_levels.finished", server.currentConfiguration());
            return true;
        }catch (OperationFailedException e) {
            taskLog.error("CLI operation failed", e);
            printer.print("tasks.logger_levels.failed", server.currentConfiguration());
            printer.print(e);
            return false;
        }
    }

    private ModelNode getLoggerLevelOp(String loggerType, String loggerName, String level) {
        final ModelNode rootLoggerOp = createEmptyOperation("write-attribute",
                PathAddress.pathAddress("subsystem", "logging").append(loggerType, loggerName));
        rootLoggerOp.get("name").set("level");
        rootLoggerOp.get("value").set(level);
        return rootLoggerOp;
    }

    public static class Config extends FlatListPostInstallConfig {
        private String rootLevel;
        private String consoleLevel;

        public Config() {
            // noop constructor for deserialization
        }

        public Config(String rootLevel, String consoleLevel) {
            this.rootLevel = rootLevel;
            this.consoleLevel = consoleLevel;
        }

        public String getRootLevel() {
            return rootLevel;
        }

        public String getConsoleLevel() {
            return consoleLevel;
        }

        @Override
        protected void acceptAttributes(Map<String, String> attributes, BiFunction<String, String, String> variableResolver) throws AutomaticInstallationParsingException {

            this.rootLevel = attributes.getOrDefault("rootLevel", null);
            this.consoleLevel = attributes.getOrDefault("consoleLevel", null);

            if (rootLevel == null || consoleLevel == null) {
                throw InstallationDataSerializer.unableToParse();
            }
        }

        @Override
        protected Map<String, String> listAttributes() {
            final HashMap<String, String> attrs = new HashMap<>();
            attrs.put("rootLevel", rootLevel);
            attrs.put("consoleLevel", consoleLevel);
            return attrs;
        }

        @Override
        protected Set<String> listVariables() {
            return Collections.emptySet();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Config config = (Config) o;
            return Objects.equals(rootLevel, config.rootLevel) && Objects.equals(consoleLevel, config.consoleLevel);
        }

        @Override
        public int hashCode() {
            return Objects.hash(rootLevel, consoleLevel);
        }

        @Override
        public String toString() {
            return "Config{" +
                    "rootLevel='" + rootLevel + '\'' +
                    ", consoleLevel='" + consoleLevel + '\'' +
                    '}';
        }
    }
}
