/*
 * Licensed Materials - Property of IBM Corp.
 * IBM UrbanCode Deploy
 * (c) Copyright IBM Corporation 2017. All Rights Reserved.
 *
 * U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
 * GSA ADP Schedule Contract with IBM Corp.
 */

package com.urbancode.air.plugin.wldeploy;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;

import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import weblogic.security.UsernameAndPassword;
import weblogic.security.UserConfigFileManager;

import com.urbancode.air.AirPluginTool;

public class WebLogicStatusHelper {

    private static String _MBEANS_SERVER_RUNTIME = "weblogic.management.mbeanservers.runtime";
    private static String _MBEANS_SERVER_DOMAINRUNTIME = "weblogic.management.mbeanservers.domainruntime";

    private static String _DOMAIN_RUNTIME_SERVICE_OBJECTNAME = new String(
            "com.bea:Name=DomainRuntimeService,Type=weblogic.management.mbeanservers.domainruntime.DomainRuntimeServiceMBean");

    private static String _DOMAIN_RUNTIME_OBJECTNAME = new String("DomainRuntime");

    private static String _SERVER_LIFE_CYCLE_RUNTIMES_OBJECTNAME = new String("ServerLifeCycleRuntimes");
    private static String _SERVER_RUNTIMES_OBJECTNAME = new String("ServerRuntimes");

    JMXConnector jmxCon = null;
    MBeanServerConnection con = null;

    public void startManagedServer(String serverName)  {
        MBeanServerConnection con = getMBeanServerConnection();

        if (con == null) {
            throw new NullPointerException("The server name " + serverName + " was not found");
        }

        try {
            ObjectName nameMatchOName = findObjectName(con, getServerLifeCycleBeans(con), serverName);

            if (nameMatchOName == null) {
                nameMatchOName= findObjectName(con, getClusterRuntimes(con), serverName);

                if ((nameMatchOName == null) || (nameMatchOName.toString().contains("ClusterRuntime"))) {
                    nameMatchOName = findObjectName(con, getClusters(), serverName);
                    if (nameMatchOName == null) {
                        System.out.println("The target name " + serverName + " was not found!");
                        System.out.println("This means it is not in the weblogic configuration.");
                        throw new NullPointerException("The target name " + serverName + " was not found!");
                    }
                    else {
                        def servers = con.getAttribute(nameMatchOName, "Servers");
                        List<String> serverNames = new ArrayList<String>();
                        servers.each { server ->
                            def name = con.getAttribute(server, "Name");
                            serverNames.add(name);
                        }
                        serverNames.each { servName ->
                            startManagedServer(servName);
                        }
                    }
                }
                else {
                    def serverNames = con.getAttribute(nameMatchOName, "ServerNames");
                    serverNames.each { Name ->
                        println "Starting server ${Name}";
                        startManagedServer(Name);
                    }
                }
            }
            else {
                 // Sending the 'start' command
                 con.invoke(nameMatchOName, "start", null, null);
            }
        } catch (Exception exc) {
            throw new RuntimeException("Error starting server!",exc);
        } finally {
        }
    }

    public boolean checkStartStatus(String[] targets, String timeout, String onFail, AirPluginTool apTool) {
        def badStatuses = true;
        def failure = false;
        def currentState;
        long startTime = System.currentTimeMillis();

        while ((badStatuses) && (System.currentTimeMillis() - startTime < (timeout.toInteger()) * 1000)) {
            Thread.sleep(10000);
            badStatuses = false;
            targets.each { target ->
                System.out.println("Checking Target ${target}");
                def status = getManagedServerStatus(target);
                if (status != "NOT_RUNNING") {
                    if (status instanceof List) {
                        status.each { serverStatusPair ->
                            if (serverStatusPair[1] != "RUNNING") {
                                if (serverStatusPair[1] != "NOT_RUNNING") {
                                    badStatuses = true;
                                }
                            }
                        }
                    } else {
                        println status;
                        if (status != "RUNNING") {
                            badStatuses = true;
                        }
                    }
                }
            }
        }
        targets.each { target ->
            currentState = getManagedServerStatus(target);

            if (!(currentState instanceof String)) {
                currentState.each { state ->
                    def curTarget = state[0];
                    def curState = state[1];
                    apTool.setOutputProperty("state-$curTarget", curState);
                    if (curState != "RUNNING") {
                        if (curState != "NOT_RUNNING") {
                            if (onFail == "fail") {
                                failure = true;
                            }
                            println("Target ${curTarget} failed to start before timing out, remains in state ${curState}");
                        }
                    }
                }
                if (failure) {
                    println("Target ${target} failed to start before timing out");
                    apTool.setOutputProperty("state-$target", "NOT_RUNNING");
                } else {
                    println("Target ${target} started successfully.");
                    apTool.setOutputProperty("state-$target", "RUNNING");
                }
            } else {
                if (currentState != "RUNNING") {
                    if (currentState != "NOT_RUNNING") {
                        if (onFail == "fail") {
                            failure = true;
                        }
                        apTool.setOutputProperty("state-${target}", currentState);
                        println("Target ${target} failed to start before timing out, remains in state ${currentState}");
                    }
                }
                else {
                    apTool.setOutputProperty("state-$target", "RUNNING");
                    println("Target ${target} started successfully.");
                }
            }
        }
        return failure;

    }

    public void stopManagedServer(String serverName, boolean force)  {
        MBeanServerConnection con = getMBeanServerConnection();

        if (con == null) {
            throw new NullPointerException("The server name " + serverName + " was not found");
        }

        try {

            ObjectName nameMatchOName = findObjectName(con, getServerLifeCycleBeans(con), serverName);

            if (nameMatchOName == null) {
                nameMatchOName= findObjectName(con, getClusterRuntimes(con), serverName);

                if ((nameMatchOName == null) || (nameMatchOName.toString().contains("ClusterRuntime"))) {
                    nameMatchOName = findObjectName(con, getClusters(), serverName);
                    if (nameMatchOName == null) {
                        System.out.println("The target name " + serverName + " was not found!");
                        System.out.println("This means it is not in the weblogic configuration.");
                        throw new NullPointerException("The target name " + serverName + " was not found!");
                    }
                    else {
                        def servers = con.getAttribute(nameMatchOName, "Servers");
                        List<String> serverNames = new ArrayList<String>();
                        servers.each { server ->
                            def name = con.getAttribute(server, "Name");
                            serverNames.add(name);
                        }
                        serverNames.each { servName ->
                            println "Stopping server ${servName}";
                            if (getManagedServerStatus(servName) != "SHUTDOWN") {
                                stopManagedServer(servName, force);
                            }
                            else {
                                println "Target ${servName} already stopped.";
                            }
                        }
                    }
                }
                else {
                    def serverNames = con.getAttribute(nameMatchOName, "ServerNames");
                    serverNames.each { Name ->
                        println "Stopping server ${Name}";
                        if (getManagedServerStatus(Name) != "SHUTDOWN") {
                            stopManagedServer(Name, force);
                        }
                        else {
                            println "Target ${Name} already stopped.";
                        }
                    }
                }
            }
            else {
                // Sending the 'shutdown' command
                def command = "shutdown";
                if (force) {
                    command = "forceShutdown";
                }
                con.invoke(nameMatchOName, command, null, null);
            }

        } catch (Exception exc) {
            throw new RuntimeException("Error stopping server" ,exc);
        } finally {
        }
    }

    public def getManagedServerStatus(String serverName)  {
        def result = null;

        MBeanServerConnection con = getMBeanServerConnection();

        if (con == null) {
            throw new NullPointerException("The server name " + serverName + " was not found");
        }

        try {

            ObjectName nameMatchOName = findObjectName(con, getServerLifeCycleBeans(con), serverName);

            if (nameMatchOName == null) {
                nameMatchOName= findObjectName(con, getClusterRuntimes(con), serverName);

                if (nameMatchOName == null) {
                    nameMatchOName = findObjectName(con, getClusters(), serverName);

                    if (nameMatchOName == null) {
                        System.out.println("The target name " + serverName + " was not found!");
                        System.out.println("This means it is not in the WebLogic configuration.");
                        throw new NullPointerException("The target name " + serverName + " was not found!");
                    }
                    else {
                        List<String> serverNames = new ArrayList<String>();

                        if (nameMatchOName) {
                            nameMatchOName = findObjectName(con, getClusters(), serverName);
                            def servers = con.getAttribute(nameMatchOName, "Servers");
                            servers.each { server ->
                                def name = con.getAttribute(server, "Name");
                                serverNames.add(name);
                            }
                        }
                        def statusList = [];
                        serverNames.each { Name ->
                            statusList << [Name, getManagedServerStatus(Name)];
                        }
                        result = statusList;
                    }
                }
                else {
                    nameMatchOName = findObjectName(con, getClusters(), serverName);
                    List<String> serverNames = new ArrayList<String>();

                    if (nameMatchOName) {
                        def servers = con.getAttribute(nameMatchOName, "Servers");
                        servers.each { server ->
                            def name = con.getAttribute(server, "Name");
                            serverNames.add(name);
                        }
                    }
                    def statusList = [];
                    serverNames.each { Name ->
                        statusList << [Name, getManagedServerStatus(Name)];
                    }
                    result = statusList;
                }
            }
            else {
                // Getting the 'state' bean
                String state = (String) con.getAttribute(nameMatchOName, "State");
                result = state;
            }

        } catch (Exception exc) {
             throw new RuntimeException("Error getting server state!", exc);
        } finally {
        }

        return result;
    }

    private ObjectName[] getServerLifeCycleBeans(MBeanServerConnection con) throws AttributeNotFoundException,
            InstanceNotFoundException, MBeanException, ReflectionException, IOException, MalformedObjectNameException,
            NullPointerException {
        ObjectName domainRunServiceOName = new ObjectName(_DOMAIN_RUNTIME_SERVICE_OBJECTNAME);
        ObjectName domainRuntimesOName = (ObjectName) con.getAttribute(domainRunServiceOName,
                _DOMAIN_RUNTIME_OBJECTNAME);
        ObjectName[] serverLifeCycleONames = (ObjectName[]) con.getAttribute(domainRuntimesOName,
                _SERVER_LIFE_CYCLE_RUNTIMES_OBJECTNAME);

        return serverLifeCycleONames;
    }

    private ObjectName getAppRuntime(MBeanServerConnection con) throws Exception {
        ObjectName domainRunServiceOName = new ObjectName(_DOMAIN_RUNTIME_SERVICE_OBJECTNAME);
        ObjectName domainRuntimesOName = (ObjectName) con.getAttribute(domainRunServiceOName,
                _DOMAIN_RUNTIME_OBJECTNAME);
        ObjectName appRuntime = (ObjectName) con.getAttribute(domainRuntimesOName, "AppRuntimeStateRuntime");
        return appRuntime;
    }

    public String[] getAppIds() throws Exception {
        ObjectName appRuntime = getAppRuntime(con);
        String [] appIds = (String[]) con.getAttribute(appRuntime, "ApplicationIds");
        return appIds;
    }

    public String getAppState(String appName, String targetName) {
        ObjectName appRuntime = getAppRuntime(con);
        String state = (String) con.invoke(appRuntime, "getCurrentState", [appName, targetName] as String[],
                                ["java.lang.String", "java.lang.String"] as String[]);
        return state;
    }

    private ObjectName[] getClusterRuntimes(MBeanServerConnection con) throws AttributeNotFoundException,
            InstanceNotFoundException, MBeanException, ReflectionException, IOException, MalformedObjectNameException,
            NullPointerException {
        ObjectName searchName = new ObjectName("com.bea:Type=ClusterRuntime,*");
        ObjectName[] clusters = con.queryNames(searchName, null);
        return clusters;
    }

    public ObjectName[] getClusters() throws AttributeNotFoundException,
            InstanceNotFoundException, MBeanException, ReflectionException, IOException, MalformedObjectNameException,
            NullPointerException {
        ObjectName searchName = new ObjectName("com.bea:Type=Cluster,*");
        ObjectName[] clusters = con.queryNames(searchName, null);
        return clusters;
    }

    public ObjectName[] getServers() throws AttributeNotFoundException,
            InstanceNotFoundException, MBeanException, ReflectionException, IOException, MalformedObjectNameException,
            NullPointerException {
        ObjectName searchName = new ObjectName("com.bea:Type=Server,*");
        ObjectName[] servers = con.queryNames(searchName, null);
        return servers;
    }

    public ObjectName[] getDomains() throws AttributeNotFoundException,
            InstanceNotFoundException, MBeanException, ReflectionException, IOException, MalformedObjectNameException,
            NullPointerException {
        ObjectName searchName = new ObjectName("com.bea:Type=Domain,*");
        ObjectName[] domains = con.queryNames(searchName, null);
        return domains;
    }

    private ObjectName findObjectName(MBeanServerConnection con, ObjectName[] serverLifeCycleONames, String match)
            throws AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException,
            IOException {
        ObjectName correctOName = null;

        for (ObjectName serverLifeCycle : serverLifeCycleONames) {

            String name = (String) con.getAttribute(serverLifeCycle, "Name");

            if (name != null && name.equals(match)) {
                correctOName = serverLifeCycle;
            }
        }

        return correctOName;
    }

    private MBeanServerConnection getMBeanServerConnection() {
        return con;
    }

    public String getUser(String configFile, String keyFile) {
        UsernameAndPassword authInfo = UserConfigFileManager.getUsernameAndPassword(configFile, keyFile, "weblogic.management");
        String user = new String(authInfo.getUsername());
        return user;
    }

    public String getPass(String configFile, String keyFile) {
        UsernameAndPassword authInfo = UserConfigFileManager.getUsernameAndPassword(configFile, keyFile, "weblogic.management");
        String pass = new String(authInfo.getPassword());
        return pass;
    }

    public String getAppsForTarget(String target) {
        def result = [];
        boolean first = true;
        con.queryNames(new ObjectName("com.bea:Type=AppDeployment,*"), null).each { deployment ->
            con.getAttribute(deployment, "Targets").each { targetON ->
                String localName = con.getAttribute(targetON, "Name");
                if (localName==target) {
                    if (first) {
                        first = false;
                        result << con.getAttribute(deployment, "Name");
                    }
                    else {
                        def app = con.getAttribute(deployment, "Name");
                        if (!result.contains(app)) {
                            result << app;
                        }
                    }
                }
            }
        }
        println "Result for $target: ${result.toString()}";
        return result;
    }

    public void initConn(String url, String username, String password) {

        MBeanServerConnection mBeanConnection = null;

        if ( (url == null) || (username == null) || (password == null) ) {
            return null;
        }

        if (!url.startsWith("service:jmx:")) {
            url = "service:jmx:" + url
        }

        try {
            JMXServiceURL serviceUrl = new JMXServiceURL(url + "/jndi/"
                    + _MBEANS_SERVER_DOMAINRUNTIME);

            Map<String, String> envMap = new HashMap<String, String>();
            envMap.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, "weblogic.management.remote");
            envMap.put(javax.naming.Context.SECURITY_PRINCIPAL, username);
            envMap.put(javax.naming.Context.SECURITY_CREDENTIALS, password);

            jmxCon = JMXConnectorFactory.newJMXConnector(serviceUrl, envMap);
            jmxCon.connect();

            mBeanConnection = jmxCon.getMBeanServerConnection();

        } catch (Exception exc) {
              throw new RuntimeException("Error connecting to weblogic!", exc);
        }
        con = mBeanConnection;
    }

    public void closeConn() {
        if (jmxCon != null) {
             try {
                 jmxCon.close();
             }
             catch (Exception e) {
                 //swallow
             }
        }
    }
}
