/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2022, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.wildfly.extension.microprofile.opentracing;

import static org.jboss.as.controller.OperationContext.Stage.MODEL;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT;
import static org.wildfly.extension.microprofile.opentracing.SubsystemExtension.EXTENSION_ADDR;

import java.util.LinkedHashMap;
import java.util.Map;

import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.OperationStepHandler;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.ProcessType;
import org.jboss.as.controller.RunningMode;
import org.jboss.as.controller.SimpleMapAttributeDefinition;
import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
import org.jboss.as.controller.StringListAttributeDefinition;
import org.jboss.as.controller.access.management.SensitiveTargetAccessConstraintDefinition;
import org.jboss.as.controller.descriptions.ResourceDescriptionResolver;
import org.jboss.as.controller.operations.MultistepUtil;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;

/**
 * @author <a href="mailto:kabir.khan@jboss.com">Kabir Khan</a>
 */
public class TracingMigrateOperation implements OperationStepHandler {
    public static final String MIGRATE = "migrate";
    public static final String MIGRATION_WARNINGS = "migration-warnings";
    public static final String MIGRATION_ERROR = "migration-error";
    public static final String MIGRATION_OPERATIONS = "migration-operations";
    public static final String DESCRIBE_MIGRATION = "describe-migration";

    // We're not transforming resources here so there will be no warnings but it is good to keep our ops consistent with other migration ops
    public static final StringListAttributeDefinition MIGRATION_WARNINGS_ATTR = new StringListAttributeDefinition.Builder(MIGRATION_WARNINGS)
            .setRequired(false)
            .build();

    public static final SimpleMapAttributeDefinition MIGRATION_ERROR_ATTR = new SimpleMapAttributeDefinition.Builder(MIGRATION_ERROR, ModelType.OBJECT, true)
            .setValueType(ModelType.OBJECT)
            .setRequired(false)
            .build();

    private boolean describe;

    static void registerOperations(ManagementResourceRegistration registry, ResourceDescriptionResolver resourceDescriptionResolver) {
        registry.registerOperationHandler(new SimpleOperationDefinitionBuilder(MIGRATE, resourceDescriptionResolver)
                        .setAccessConstraints(SensitiveTargetAccessConstraintDefinition.READ_WHOLE_CONFIG)
                        .setReplyParameters(MIGRATION_WARNINGS_ATTR, MIGRATION_ERROR_ATTR)
                        .build(),
                new TracingMigrateOperation(false));
        registry.registerOperationHandler(new SimpleOperationDefinitionBuilder(DESCRIBE_MIGRATION, resourceDescriptionResolver)
                        .setAccessConstraints(SensitiveTargetAccessConstraintDefinition.READ_WHOLE_CONFIG)
                        .setReplyParameters(MIGRATION_WARNINGS_ATTR)
                        .setReadOnly()
                        .build(),
                new TracingMigrateOperation(true));
    }

    private TracingMigrateOperation(boolean describe) {
        this.describe = describe;
    }

    @Override
    public void execute(OperationContext context, ModelNode modelNode) throws OperationFailedException {
        if (!describe && context.getRunningMode() != RunningMode.ADMIN_ONLY) {
            throw TracingExtensionLogger.ROOT_LOGGER.migrateOperationAllowedOnlyInAdminOnly();
        }

        LinkedHashMap<PathAddress, ModelNode> operations = new LinkedHashMap<>();

        context.addStep(new OperationStepHandler() {
            @Override
            public void execute(OperationContext context, ModelNode modelNode) throws OperationFailedException {
                addOpsToRemoveSubsystemAndExtension(operations, context);

                if (describe) {
                    ModelNode result = new ModelNode();
                    result.get(MIGRATION_OPERATIONS).set(operations.values());
                } else {
                    final Map<PathAddress, ModelNode> migrateOpResponses = new LinkedHashMap<>();
                    MultistepUtil.recordOperationSteps(context, operations, migrateOpResponses);

                    context.completeStep(new OperationContext.ResultHandler() {
                        @Override
                        public void handleResult(OperationContext.ResultAction resultAction, OperationContext context, ModelNode modelNode) {
                            final ModelNode result = new ModelNode();
                            ModelNode rw = new ModelNode().setEmptyList();
                            result.get(MIGRATION_WARNINGS).set(rw);
                            if (resultAction == OperationContext.ResultAction.ROLLBACK) {
                                for (Map.Entry<PathAddress, ModelNode> entry : migrateOpResponses.entrySet()) {
                                    if (entry.getValue().hasDefined(FAILURE_DESCRIPTION)) {
                                        //we check for failure description, as every node has 'failed', but one
                                        //the real error has a failure description
                                        //we break when we find the first one, as there will only ever be one failure
                                        //as the op stops after the first failure
                                        ModelNode desc = new ModelNode();
                                        desc.get(OP).set(operations.get(entry.getKey()));
                                        desc.get(RESULT).set(entry.getValue());
                                        result.get(MIGRATION_ERROR).set(desc);
                                        break;
                                    }
                                }
                                context.getFailureDescription().set(new ModelNode(TracingExtensionLogger.ROOT_LOGGER.migrationFailed()));
                            }
                            context.getResult().set(result);
                        }
                    });
                }
            }
        }, MODEL);
    }

    private void addOpsToRemoveSubsystemAndExtension(LinkedHashMap<PathAddress, ModelNode> operations, OperationContext context) {
        ProcessType processType = context.getCallEnvironment().getProcessType();
        boolean domainMode = processType != ProcessType.STANDALONE_SERVER && processType != ProcessType.SELF_CONTAINED;

        // Remove the subsystem
        operations.put(context.getCurrentAddress(), Util.createRemoveOperation(context.getCurrentAddress()));
        if (!domainMode) {
            operations.put(EXTENSION_ADDR, Util.createRemoveOperation(EXTENSION_ADDR));
        }
    }

}
