/*
Copyright 2016 Red Hat, Inc.

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.as.test.integration.domain.suites;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.BLOCKING;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.COMPOSITE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.EXTENSION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INET_ADDRESS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INTERFACE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.LOCAL_DESTINATION_OUTBOUND_SOCKET_BINDING;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.LOOPBACK;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATION_REQUIRES_RELOAD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PORT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROFILE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RELOAD_SERVERS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESPONSE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESPONSE_HEADERS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER_GROUP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER_GROUPS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SOCKET_BINDING;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SOCKET_BINDING_GROUP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SOCKET_BINDING_REF;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SOURCE_PORT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STEPS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
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;

import java.io.IOException;

import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.test.integration.domain.management.util.DomainLifecycleUtil;
import org.jboss.as.test.integration.domain.management.util.DomainTestSupport;
import org.jboss.as.test.integration.management.extension.ExtensionUtils;
import org.jboss.as.test.integration.management.extension.dependent.DependentExtension;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Test of WFCORE-1106 behavior.
 *
 * @author Brian Stansberry
 */
public class CapabilityReloadRequiredUnitTestCase {

    private static final String WFCORE1106 = "WFCORE-1106";

    private static final String WFCORE1106_OSB = WFCORE1106 + "-osb";

    private static final String WFCORE1106_OSB2 = WFCORE1106_OSB + "-2";
    private static final PathAddress EXT = PathAddress.pathAddress(EXTENSION, WFCORE1106);
    private static final PathAddress IFACE = PathAddress.pathAddress(INTERFACE, WFCORE1106);
    private static final PathAddress SB = PathAddress.pathAddress(SOCKET_BINDING_GROUP, "other-sockets").append(SOCKET_BINDING, WFCORE1106);
    private static final PathAddress OSB = PathAddress.pathAddress(SOCKET_BINDING_GROUP, "other-sockets").append(LOCAL_DESTINATION_OUTBOUND_SOCKET_BINDING, WFCORE1106_OSB);
    private static final PathAddress OSB_2 = PathAddress.pathAddress(SOCKET_BINDING_GROUP, "other-sockets").append(LOCAL_DESTINATION_OUTBOUND_SOCKET_BINDING, WFCORE1106_OSB2);
    private static final PathAddress SUB = PathAddress.pathAddress(PROFILE, "other").append(SUBSYSTEM, DependentExtension.SUBSYSTEM_NAME);

    private static DomainTestSupport testSupport;
    private static DomainLifecycleUtil domainMasterLifecycleUtil;

    @BeforeClass
    public static void setupDomain() throws Exception {
        ExtensionUtils.createExtensionModule(WFCORE1106, DependentExtension.class);
        testSupport = DomainTestSuite.createSupport(CapabilityReloadRequiredUnitTestCase.class.getSimpleName());
        domainMasterLifecycleUtil = testSupport.getDomainMasterLifecycleUtil();
    }

    @AfterClass
    public static void tearDownDomain() throws Exception {
        try {
            testSupport = null;
            domainMasterLifecycleUtil = null;
            DomainTestSuite.stopSupport();
        } finally {
            ExtensionUtils.deleteExtensionModule(WFCORE1106);
        }
    }

    @Before
    public void setup() throws IOException {

        ModelNode addOp = Util.createAddOperation(IFACE);
        addOp.get(LOOPBACK).set(true);
        executeOp(addOp);

        addOp = Util.createAddOperation(SB);
        addOp.get(INTERFACE).set(WFCORE1106);
        addOp.get(PORT).set(12345);
        executeOp(addOp);

        addOp = Util.createAddOperation(OSB);
        addOp.get(SOCKET_BINDING_REF).set(WFCORE1106);
        executeOp(addOp);

        addOp = Util.createAddOperation(OSB_2);
        addOp.get(SOCKET_BINDING_REF).set(WFCORE1106);
        executeOp(addOp);

        addOp = Util.createAddOperation(EXT);
        executeOp(addOp);
    }

    @After
    public void tearDown() throws IOException {

        IOException ioe = null;
        AssertionError ae = null;
        RuntimeException re = null;

        PathAddress[] cleanUp = {SUB, EXT, OSB, OSB_2, SB};
        for (int i = 0; i < cleanUp.length; i++) {
            try {
                executeOp(Util.createRemoveOperation(cleanUp[i]));
            } catch (IOException e) {
                if (ioe == null) {
                    ioe = e;
                }
            } catch (AssertionError e) {
                if (i > 0 && i < cleanUp.length - 1 && ae == null) {
                    ae = e;
                } // else ignore because in a failed test SUB may not exist, causing remove to fail
                  // and because removing the interface will definitely fail
            } catch (RuntimeException e) {
                if (re == null) {
                    re = e;
                }
            }
        }

        try {
            ModelNode op = Util.createEmptyOperation(RELOAD_SERVERS, PathAddress.pathAddress(SERVER_GROUP, "other-server-group"));
            op.get(BLOCKING).set(true);
            executeOp(op);
        } catch (IOException e) {
            if (ioe == null) {
                ioe = e;
            }
        } catch (AssertionError e) {
            if (ae == null) {
                ae = e;
            }
        } catch (RuntimeException e) {
            if (re == null) {
                re = e;
            }
        }

        try {
            executeOp(Util.createRemoveOperation(IFACE));
        } catch (IOException e) {
            if (ioe == null) {
                ioe = e;
            }
        } catch (AssertionError e) {
            if (ae == null) {
                ae = e;
            }
        } catch (RuntimeException e) {
            if (re == null) {
                re = e;
            }
        }

        if (ioe != null) {
            throw ioe;
        }

        if (ae != null) {
            throw ae;
        }

        if (re != null) {
            throw re;
        }
    }

    @Test
    public void testCapabilityReloadEffects() throws IOException {
        // Change the interface to put its capability into r-r
        ModelNode op = Util.createEmptyOperation(COMPOSITE, PathAddress.EMPTY_ADDRESS);
        ModelNode steps = op.get(STEPS);
        steps.add(Util.getUndefineAttributeOperation(IFACE, LOOPBACK));
        steps.add(Util.getWriteAttributeOperation(IFACE, INET_ADDRESS, "${jboss.bind.address:127.0.0.1}"));
        assertStepRequiresReload(op, "main-server-group", "other-server-group");

        // Add the loc -- should get a restart-required header
        op = Util.createAddOperation(SUB);
        op.get("osb").set(WFCORE1106_OSB);
        assertStepRequiresReload(op);

        // reload
        op = Util.createEmptyOperation(RELOAD_SERVERS, PathAddress.pathAddress(SERVER_GROUP, "other-server-group"));
        op.get(BLOCKING).set(true);
        executeOp(op);

        // Change the l-o-c -- should *not* get a restart-required header
        op = Util.getWriteAttributeOperation(SUB, "osb", WFCORE1106_OSB2);

        assertStepDoesNotRequireReload(op);

        // Change the o-s-b to put its capability into r-r

        op = Util.getWriteAttributeOperation(OSB, SOURCE_PORT, 54321);
        assertStepRequiresReload(op);

        // Change the l-o-c -- should get a restart-required header
        op = Util.getWriteAttributeOperation(SUB, "osb", WFCORE1106_OSB);
        assertStepRequiresReload(op);

        // Remove the subsystem and osb
        op = Util.createEmptyOperation(COMPOSITE, PathAddress.EMPTY_ADDRESS);
        steps = op.get(STEPS);
        steps.add(Util.createRemoveOperation(SUB));
        steps.add(Util.createRemoveOperation(OSB));
        assertStepRequiresReload(op);

        // Re-add  -- should still get a restart-required header on SUB
        // because removing the o-s-b capability doesn't flush the need to reload
        // We also get a r-r header on the o-s-b add
        op = Util.createAddOperation(OSB);
        op.get(SOCKET_BINDING_REF).set(WFCORE1106);
        assertStepRequiresReload(op);
        op = Util.createAddOperation(SUB);
        op.get("osb").set(WFCORE1106_OSB);
        assertStepRequiresReload(op);

    }

    private void assertStepRequiresReload(ModelNode op) throws IOException {
        assertRequiresReload(op, true, "other-server-group");
    }

    private void assertStepRequiresReload(ModelNode op, String... serverGroups) throws IOException {
        assertRequiresReload(op, true, serverGroups);
    }

    private void assertStepDoesNotRequireReload(ModelNode op) throws IOException {
        assertRequiresReload(op, false, "other-server-group");
    }

    private void assertRequiresReload(ModelNode op, boolean expectRequires, String... serverGroups) throws IOException {
        ModelNode origResp = executeOp(op);
        for (String sg : serverGroups) {
            for (Property prop : origResp.get(SERVER_GROUPS, sg, HOST).asPropertyList()) {
                String host = prop.getName();
                for (Property prop2 : prop.getValue().asPropertyList()) {
                    String server = prop2.getName();
                    ModelNode response = prop2.getValue().get(RESPONSE);
                    // Hack for composites
                    if (COMPOSITE.equals(op.get(OP).asString())) {
                        response = response.get(RESULT, "step-1");
                    }
                    String target = " " + sg + "/" + host + "/" + server;
                    if (expectRequires) {
                        assertTrue(origResp.toString() + target, response.hasDefined(RESPONSE_HEADERS, OPERATION_REQUIRES_RELOAD));
                        assertTrue(origResp.toString() + target, response.get(RESPONSE_HEADERS, OPERATION_REQUIRES_RELOAD).asBoolean());
                    } else {
                        assertFalse(origResp.toString() + target, response.hasDefined(RESPONSE_HEADERS, OPERATION_REQUIRES_RELOAD));
                    }
                }
            }
        }
    }

    private ModelNode executeOp(ModelNode op) throws IOException {
        ModelNode response = domainMasterLifecycleUtil.getDomainClient().execute(op);
        assertTrue(response.toString(), response.hasDefined(OUTCOME));
        assertEquals(response.toString(), SUCCESS, response.get(OUTCOME).asString());
        return response;
    }
}
