/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2012, 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.jboss.as.test.integration.domain.suites;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CHILD_TYPE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_CHILDREN_NAMES_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.management.InstanceNotFoundException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import org.jboss.as.controller.client.helpers.domain.DomainClient;
import org.jboss.as.jmx.model.ModelControllerMBeanHelper;
import org.jboss.as.network.NetworkUtils;
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.domain.management.util.WildFlyManagedConfiguration;
import org.jboss.dmr.ModelNode;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.xnio.IoUtils;

/**
 *
 * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
 */
public class JMXHostSubsystemTestCase {

    private static final String RESOLVED_DOMAIN = "jboss.as";

    static final ObjectName RESOLVED_MODEL_FILTER = createObjectName(RESOLVED_DOMAIN  + ":*");
    static final ObjectName RESOLVED_ROOT_MODEL_NAME = ModelControllerMBeanHelper.createRootObjectName(RESOLVED_DOMAIN);


    private static DomainTestSupport testSupport;
    private static DomainLifecycleUtil domainMasterLifecycleUtil;
    private static DomainLifecycleUtil domainSlaveLifecycleUtil;
    private DomainClient masterClient;
    private DomainClient slaveClient;
    JMXConnector masterConnector;
    MBeanServerConnection masterConnection;
    JMXConnector slaveConnector;
    MBeanServerConnection slaveConnection;


    @BeforeClass
    public static void setupDomain() throws Exception {
        testSupport = DomainTestSuite.createSupport(JMXHostSubsystemTestCase.class.getSimpleName());
        domainMasterLifecycleUtil = testSupport.getDomainMasterLifecycleUtil();
        domainSlaveLifecycleUtil = testSupport.getDomainSlaveLifecycleUtil();
    }

    @AfterClass
    public static void tearDownDomain() throws Exception {
        testSupport = null;
        domainMasterLifecycleUtil = null;
        domainSlaveLifecycleUtil = null;
        DomainTestSuite.stopSupport();
    }


    @Before
    public void initialize() throws Exception {
        masterClient = domainMasterLifecycleUtil.getDomainClient();
        masterConnector = setupAndGetConnector(domainMasterLifecycleUtil);
        masterConnection = masterConnector.getMBeanServerConnection();
        slaveClient = domainSlaveLifecycleUtil.getDomainClient();
        slaveConnector = setupAndGetConnector(domainSlaveLifecycleUtil);
        slaveConnection = slaveConnector.getMBeanServerConnection();
    }

    @After
    public void closeConnection() throws Exception {
        IoUtils.safeClose(masterConnector);
        IoUtils.safeClose(slaveConnector);
    }

    /**
     * Test that all the MBean infos can be read properly
     */
    @Test
    public void testAllMBeanInfosMaster() throws Exception {
        testAllMBeanInfos(masterConnection);
    }

    /**
     * Test that all the MBean infos can be read properly
     */
    @Test
    public void testAllMBeanInfosSlave() throws Exception {
        testAllMBeanInfos(slaveConnection);
    }

    private void testAllMBeanInfos(MBeanServerConnection connection) throws Exception {
        Set<ObjectName> names = connection.queryNames(RESOLVED_MODEL_FILTER, null);
        Map<ObjectName, Exception> failedInfos = new HashMap<ObjectName, Exception>();

        for (ObjectName name : names) {
            try {
                Assert.assertNotNull(connection.getMBeanInfo(name));
            } catch (Exception e) {
                System.out.println("Error getting info for " + name);
                failedInfos.put(name, e);
            }
        }
        Assert.assertTrue(failedInfos.toString(), failedInfos.isEmpty());
    }

    @Test
    public void testSystemPropertiesMaster() throws Exception {
        //testDomainModelSystemProperties(masterClient, true, masterConnection);
        //For now disable writes on the master while we decide if it is a good idea or not
        testDomainModelSystemProperties(masterClient, false, masterConnection);
    }

    @Test
    public void testSystemPropertiesSlave() throws Exception {
        testDomainModelSystemProperties(slaveClient, false, slaveConnection);
    }

    private void testDomainModelSystemProperties(DomainClient client, boolean master, MBeanServerConnection connection) throws Exception {
        String[] initialNames = getSystemPropertyNames(client);

        ObjectName testName = new ObjectName(RESOLVED_DOMAIN + ":system-property=mbeantest");
        assertNoMBean(testName, connection);

        MBeanInfo info = connection.getMBeanInfo(RESOLVED_ROOT_MODEL_NAME);
        MBeanOperationInfo opInfo = null;
        for (MBeanOperationInfo op : info.getOperations()) {
            if (op.getName().equals("addSystemProperty")) {
                Assert.assertNull(opInfo); //Simple check to guard against the op being overloaded
                opInfo = op;

            }
        }
        Assert.assertEquals(master, opInfo != null);

        try {
            connection.invoke(RESOLVED_ROOT_MODEL_NAME, "addSystemProperty", new Object[] {"mbeantest", false, "800"}, new String[] {String.class.getName(), Boolean.class.getName(), String.class.getName()});
            Assert.assertTrue(master);//The invoke should not work if it is a slave since the domain model is only writable from the slave
        } catch (Exception e) {
            if (master) {
                //There should be no exception executing the invoke from a master HC
                throw e;
            }
            //Expected for a slave; we can't do any more
            return;
        }
        try {
            String[] newNames = getSystemPropertyNames(client);
            Assert.assertEquals(initialNames.length + 1, newNames.length);
            boolean found = false;
            for (String s : newNames) {
                if (s.equals("mbeantest")) {
                    found = true;
                    break;
                }
            }
            Assert.assertTrue(found);
            Assert.assertNotNull(connection.getMBeanInfo(new ObjectName(RESOLVED_DOMAIN + ":system-property=mbeantest")));
        } finally {
            connection.invoke(new ObjectName(RESOLVED_DOMAIN + ":system-property=mbeantest"), "remove", new Object[0], new String[0]);
        }

        assertNoMBean(testName, connection);

        Assert.assertEquals(initialNames.length, getSystemPropertyNames(client).length);
    }

    @Test
    public void testNoSlaveMBeansVisibleFromMaster() throws Exception {
        testNoMBeansVisible(masterConnection, true);
    }

    @Test
    public void testNoMasterMBeansVisibleFromSlave() throws Exception {
        testNoMBeansVisible(slaveConnection, false);
    }

    private void testNoMBeansVisible(MBeanServerConnection connection, boolean master) throws Exception {
        String pattern = "jboss.as:host=%s,extension=org.jboss.as.jmx";
        ObjectName mine = createObjectName(String.format(pattern, master ? "master" : "slave"));
        ObjectName other = createObjectName(String.format(pattern, master ? "slave" : "master"));
        assertNoMBean(other, connection);
        Assert.assertNotNull(connection.getMBeanInfo(mine));
    }

    private void assertNoMBean(ObjectName name, MBeanServerConnection connection) throws Exception {
        try {
            connection.getMBeanInfo(name);
            Assert.fail("Should not have found mbean with nane " + name);
        } catch (InstanceNotFoundException expected) {
        }
    }

    private String[] getSystemPropertyNames(DomainClient client) throws Exception {
        ModelNode op = new ModelNode();
        op.get(OP).set(READ_CHILDREN_NAMES_OPERATION);
        op.get(OP_ADDR).setEmptyList();
        op.get(CHILD_TYPE).set("system-property");

        ModelNode result = client.execute(op);
        Assert.assertEquals(SUCCESS, result.get(OUTCOME).asString());
        List<ModelNode> propertyNames = result.get(RESULT).asList();
        String[] names = new String[propertyNames.size()];
        int i = 0;
        for (ModelNode node : propertyNames) {
            names[i++] = node.asString();
        }
        return names;
    }


    private static ObjectName createObjectName(String name) {
        try {
            return ObjectName.getInstance(name);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private JMXConnector setupAndGetConnector(DomainLifecycleUtil util) throws Exception {
        WildFlyManagedConfiguration config = util.getConfiguration();
        // Make sure that we can connect to the MBean server
        String urlString = System
                .getProperty("jmx.service.url", "service:jmx:remoting-jmx://" + NetworkUtils.formatPossibleIpv6Address(config.getHostControllerManagementAddress()) + ":" + config.getHostControllerManagementPort());
        JMXServiceURL serviceURL = new JMXServiceURL(urlString);
        return JMXConnectorFactory.connect(serviceURL, null);
    }
}
