/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */
package org.wildfly.core.test.standalone.mgmt;

import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.test.integration.management.extension.EmptySubsystemParser;
import org.jboss.as.test.integration.management.extension.ExtensionUtils;
import org.jboss.as.test.integration.management.extension.optypes.OpTypesExtension;
import org.jboss.dmr.ModelNode;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.wildfly.core.testrunner.ManagementClient;
import org.wildfly.core.testrunner.WildFlyRunner;

import jakarta.inject.Inject;
import java.io.IOException;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
 * Tests adding and removing extensions and subsystems in a composite op.
 *
 * @author Brian Stansberry
 */
@RunWith(WildFlyRunner.class)
public class ExtensionSubsystemCompositeTestCase {

    private static final PathAddress EXT = PathAddress.pathAddress("extension", OpTypesExtension.EXTENSION_NAME);
    private static final PathAddress SUBSYSTEM = PathAddress.pathAddress("subsystem", OpTypesExtension.SUBSYSTEM_NAME);

    @Inject
    private static ManagementClient managementClient;

    @Before
    public void installExtensionModule() throws IOException {
        // We use OpTypesExtension for this test because it's convenient. Doesn't do anything crazy
        // and exposes an op ('public') that we can call to check the subsystem is added and functions
        ExtensionUtils.createExtensionModule(OpTypesExtension.EXTENSION_NAME, OpTypesExtension.class,
                EmptySubsystemParser.class.getPackage());
    }

    @After
    public void removeExtensionModule() {

        try {
            executeOp(Util.createRemoveOperation(SUBSYSTEM), SUCCESS);
        } catch (Throwable ignored) {
            // assume subsystem wasn't there
        } finally {
            try {
                executeOp(Util.createRemoveOperation(EXT), SUCCESS);
            } catch (Throwable t) {
                // assume extension wasn't there
            } finally {
                ExtensionUtils.deleteExtensionModule(OpTypesExtension.EXTENSION_NAME);
            }
        }
    }

    @Test
    public void test() throws IOException {

        // 1) Sanity check -- subsystem not there
        ModelNode invokePublic = Util.createEmptyOperation("public", SUBSYSTEM);
        testBadOp(invokePublic);

        // 2) sanity check -- subsystem add w/o extension -- fail
        ModelNode subAdd = Util.createAddOperation(SUBSYSTEM);
        testBadOp(subAdd);

        // 3) ext add + sub add + sub other in composite
        ModelNode extAdd = Util.createAddOperation(EXT);
        ModelNode goodAdd = buildComposite(extAdd, subAdd, invokePublic);
        testGoodComposite(goodAdd);

        // 4) Sanity check -- try invokePublic again outside the composite
        ModelNode response = executeOp(invokePublic, "success");
        assertTrue(response.toString(), response.has("result"));
        assertTrue(response.toString(), response.get("result").asBoolean());

        // 5) sub remove + ext remove + sub add in composite -- fail
        ModelNode subRemove = Util.createRemoveOperation(SUBSYSTEM);
        ModelNode extRemove = Util.createRemoveOperation(EXT);
        ModelNode badRemove = buildComposite(invokePublic, subRemove, extRemove, subAdd);
        response = testBadOp(badRemove);
        // But the 'public' op should have worked
        validateInvokePublicStep(response, 1, true);

        // 6) sub remove + ext remove in composite
        ModelNode goodRemove = buildComposite(invokePublic, subRemove, extRemove);
        response = executeOp(goodRemove, "success");
        validateInvokePublicStep(response, 1, false);

        // 7) confirm ext add + sub add + sub other still works
        testGoodComposite(goodAdd);

        // 8) Sanity check -- try invokePublic again outside the composite
        response = executeOp(invokePublic, "success");
        assertTrue(response.toString(), response.has("result"));
        assertTrue(response.toString(), response.get("result").asBoolean());
    }

    private ModelNode executeOp(ModelNode op, String outcome) throws IOException {
        ModelNode response = managementClient.getControllerClient().execute(op);
        assertTrue(response.toString(), response.hasDefined(OUTCOME));
        assertEquals(response.toString(), outcome, response.get(OUTCOME).asString());
        return response;
    }

    private void testGoodComposite(ModelNode composite) throws IOException {
        ModelNode result = executeOp(composite, "success");
        validateInvokePublicStep(result, 3, false);
    }

    private ModelNode testBadOp(ModelNode badOp) throws IOException {
        ModelNode response = executeOp(badOp, "failed");
        String msg = response.toString();
        assertTrue(msg, response.has("failure-description"));
        ModelNode failure = response.get("failure-description");
        assertTrue(msg, failure.asString().contains("WFLYCTL0030"));
        return response;
    }

    private static ModelNode buildComposite(ModelNode... steps) {
        ModelNode result = Util.createEmptyOperation("composite", PathAddress.EMPTY_ADDRESS);
        ModelNode stepsParam = result.get("steps");
        for (ModelNode step : steps) {
            stepsParam.add(step);
        }
        return result;
    }

    private static void validateInvokePublicStep(ModelNode response, int step, boolean expectRollback) {
        String msg = response.toString();
        assertTrue(msg, response.has("result"));
        ModelNode result = response.get("result");
        assertTrue(msg, result.isDefined());
        String stepKey = "step-"+step;
        assertEquals(msg, expectRollback ? "failed" : "success", result.get(stepKey, "outcome").asString());
        assertTrue(msg, result.has(stepKey, "result"));
        assertTrue(msg, result.get(stepKey, "result").asBoolean());
        if (expectRollback) {
            assertTrue(msg, result.has(stepKey, "rolled-back"));
            assertTrue(msg, result.get(stepKey, "rolled-back").asBoolean());
        } else {
            assertFalse(msg, result.has(stepKey, "rolled-back"));
        }
    }

}
