/**
 * Copyright (c) 2014 SUSE LLC
 *
 * This software is licensed to you under the GNU General Public License,
 * version 2 (GPLv2). There is NO WARRANTY for this software, express or
 * implied, including the implied warranties of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
 * along with this software; if not, see
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
 *
 * Red Hat trademarks are not licensed under GPLv2. No permission is
 * granted to use or replicate Red Hat trademarks that are incorporated
 * in this software or its documentation.
 */
package com.redhat.rhn.manager.action;

import com.redhat.rhn.domain.action.Action;
import com.redhat.rhn.domain.action.ActionChain;
import com.redhat.rhn.domain.action.ActionChainFactory;
import com.redhat.rhn.domain.action.ActionFactory;
import com.redhat.rhn.domain.action.ActionType;
import com.redhat.rhn.domain.action.config.ConfigAction;
import com.redhat.rhn.domain.action.rhnpackage.PackageAction;
import com.redhat.rhn.domain.action.script.ScriptActionDetails;
import com.redhat.rhn.domain.action.script.ScriptRunAction;
import com.redhat.rhn.domain.errata.Errata;
import com.redhat.rhn.domain.server.Server;
import com.redhat.rhn.domain.server.ServerFactory;
import com.redhat.rhn.domain.user.User;
import com.redhat.rhn.manager.errata.ErrataManager;
import com.redhat.rhn.manager.system.SystemManager;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * An ActionManager companion to deal with Action Chains.
 *
 * Methods in this class are intended to replace similar methods in
 * ActionManager adding Action Chains support. It was decided to keep
 * this class separate to avoid adding significant complexity to ActionManager.
 * @author Silvio Moioli {@literal <smoioli@suse.de>}
 */
public class ActionChainManager {

    /**
     * Utility class constructor.
     */
    private ActionChainManager() {
    }

    /**
     * Schedules s package installation on a server.
     * @param user the user scheduling actions
     * @param server the server
     * @param packages the packages
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled action
     * @see com.redhat.rhn.manager.action.ActionManager#schedulePackageInstall
     */
    public static PackageAction schedulePackageInstall(User user, Server server,
        List<Map<String, Long>> packages, Date earliest, ActionChain actionChain) {
        return schedulePackageActionByOs(user, server, packages, earliest, actionChain,
                null, ActionFactory.TYPE_PACKAGES_UPDATE);
    }

    /**
     * Schedules s package removal on a server.
     * @param user the user scheduling actions
     * @param server the server
     * @param packages the packages
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled action
     * @see com.redhat.rhn.manager.action.ActionManager#schedulePackageRemoval
     */
    public static PackageAction schedulePackageRemoval(User user, Server server,
        List<Map<String, Long>> packages, Date earliest, ActionChain actionChain) {
        return schedulePackageActionByOs(user, server, packages, earliest, actionChain,
                null, ActionFactory.TYPE_PACKAGES_REMOVE);
    }

    /**
     * Schedules package upgrade(s) for the given servers.
     * Note: package upgrade = package install
     * @param user the user scheduling actions
     * @param packageMaps maps system IDs to lists of "package maps"
     * @param earliestAction Date of earliest action to be executed
     * @param actionChain the action chain or null
     * @return scheduled actions
     * @see ActionManager#addPackageActionDetails(Action, List) for "package map"
     */
    public static List<Action> schedulePackageUpgrades(User user,
            Map<Long, List<Map<String, Long>>> packageMaps, Date earliestAction,
            ActionChain actionChain) {
        List<Action> actions = new ArrayList<Action>();

        Integer sortOrder = null;
        if (actionChain != null) {
            sortOrder = ActionChainFactory.getNextSortOrderValue(actionChain);
        }
        for (Long sid : packageMaps.keySet()) {
            Server server = SystemManager.lookupByIdAndUser(sid, user);
            actions.add(schedulePackageActionByOs(user, server, packageMaps.get(sid),
                    earliestAction, actionChain, sortOrder,
                    ActionFactory.TYPE_PACKAGES_UPDATE));
        }
        return actions;
    }

    /**
     * Schedules a package upgrade for the given server.
     * @param user the user scheduling actions
     * @param server the server
     * @param packages a list of "package maps"
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled action
     * @see ActionManager#addPackageActionDetails(Action, List) for "package map"
     */
    public static PackageAction schedulePackageUpgrade(User user, Server server,
        List<Map<String, Long>> packages, Date earliest, ActionChain actionChain) {
        return schedulePackageInstall(user, server, packages, earliest, actionChain);
    }

    /**
     * Schedules a package verification for the given server.
     * @param user the user scheduling actions
     * @param server the server
     * @param packages a list of "package maps"
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled action
     * @see com.redhat.rhn.manager.action.ActionManager#schedulePackageVerify
     * @see ActionManager#addPackageActionDetails(Action, List) for "package map"
     */
    public static PackageAction schedulePackageVerify(User user, Server server,
        List<Map<String, Long>> packages, Date earliest, ActionChain actionChain) {
        return (PackageAction) schedulePackageAction(user, packages,
            ActionFactory.TYPE_PACKAGES_VERIFY, earliest, actionChain, null, server);
    }


    /**
     * Schedules a generic package action on a single server.
     * @param user the user scheduling actions
     * @param packages a list of "package maps"
     * @param type the type
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @param sortOrder the sort order or null
     * @param server the server
     * @return scheduled action
     * @see com.redhat.rhn.manager.action.ActionManager#schedulePackageAction
     * @see ActionManager#addPackageActionDetails(Action, List) for "package map"
     */
    private static Action schedulePackageAction(User user,
        List<Map<String, Long>> packages, ActionType type, Date earliest,
        ActionChain actionChain, Integer sortOrder, Server server) {
        Set<Long> serverIds = new HashSet<Long>();
        serverIds.add(server.getId());
        return schedulePackageActions(user, packages, type, earliest, actionChain,
            sortOrder, serverIds).iterator().next();
    }

    /**
     * Schedules generic package actions on multiple servers.
     * @param user the user scheduling actions
     * @param packages a list of "package maps"
     * @param type the type
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @param sortOrder the sort order or null
     * @param servers the servers involved
     * @return scheduled actions
     * @see com.redhat.rhn.manager.action.ActionManager#schedulePackageAction
     * @see ActionManager#addPackageActionDetails(Action, List) for "package map"
     */
    private static Set<Action> schedulePackageActions(User user,
        List<Map<String, Long>> packages, ActionType type, Date earliestAction,
        ActionChain actionChain, Integer sortOrder, Set<Long> serverIds) {

        String name = ActionManager.getActionName(type);

        Set<Action> result = scheduleActions(user, type, name, earliestAction,
            actionChain, sortOrder, serverIds);

        for (Action action : result) {
            ActionManager.addPackageActionDetails(action, packages);
        }

        return result;
    }

    /**
     * Schedules script actions for the given servers.
     * @param user the user scheduling actions
     * @param sids IDs of affected servers
     * @param script detail object of the script to run
     * @param name name of the script action
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled actions
     * @throws com.redhat.rhn.manager.MissingCapabilityException if any server
     *             in the list is missing script.run schedule fails
     * @throws com.redhat.rhn.manager.MissingEntitlementException if any server
     *             in the list is missing Provisioning schedule fails
     * @see com.redhat.rhn.manager.action.ActionManager#scheduleScriptRun
     * @see ActionManager#addPackageActionDetails(Action, List) for "package map"
     */
    public static Set<Action> scheduleScriptRuns(User user, List<Long> sids,
        String name, ScriptActionDetails script, Date earliest, ActionChain actionChain) {

        ActionManager.checkScriptingOnServers(sids);

        Set<Long> sidSet = new HashSet<Long>();
        sidSet.addAll(sids);

        Set<Action> result = scheduleActions(user, ActionFactory.TYPE_SCRIPT_RUN, name,
            earliest, actionChain, null, sidSet);
        for (Action action : result) {
            ScriptActionDetails actionScript = ActionFactory.createScriptActionDetails(
                    script.getUsername(), script.getGroupname(), script.getTimeout(),
                    script.getScriptContents());
            ((ScriptRunAction)action).setScriptActionDetails(actionScript);
            ActionFactory.save(action);
        }
        return result;
    }

    /**
     * Creates configuration actions for the given server IDs.
     * @param user the user scheduling actions
     * @param revisions a set of revision IDs
     * @param serverIds a set of server IDs
     * @param type the type of configuration action
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled actions
     */
    public static Set<Action> createConfigActions(User user, Collection<Long> revisions,
        Collection<Long> serverIds, ActionType type, Date earliest,
        ActionChain actionChain) {

        List <Server> servers = SystemManager.hydrateServerFromIds(serverIds, user);
        return createConfigActionForServers(user, revisions, servers, type, earliest,
            actionChain);
    }

    /**
     * Creates configuration actions for the given servers.
     * @param user the user scheduling actions
     * @param revisions a set of revision IDs
     * @param servers a set of servers
     * @param type the type of configuration action
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled actions
     */
    public static Set<Action> createConfigActionForServers(User user,
        Collection<Long> revisions, Collection<Server> servers, ActionType type,
        Date earliest, ActionChain actionChain) {
        Set<Action> result = new HashSet<Action>();
        if (actionChain == null) {
            Action action = ActionManager.createConfigActionForServers(user, revisions,
                servers, type, earliest);
            if (action != null) {
                result.add(action);
            }
        }
        else {
            for (Server server : servers) {
                ConfigAction action = ActionManager
                    .createConfigAction(user, type, earliest);
                ActionManager.checkConfigActionOnServer(type, server);
                ActionChainFactory.queueActionChainEntry(action, actionChain, server);
                ActionManager.addConfigurationRevisionsToAction(user, revisions, action,
                    server);
                ActionFactory.save(action);
                result.add(action);
            }
        }
        return result;
    }

    /**
     * Creates configuration actions from server-revision maps.
     * @param user the user scheduling actions
     * @param revisions maps servers to multiple revision IDs
     * @param serverIds a set of server IDs
     * @param type the type of configuration action
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled actions
     */
    public static Set<Action> createConfigActions(User user,
            Map<Long, Collection<Long>> revisions, Collection<Long> serverIds,
            ActionType type, Date earliest, ActionChain actionChain) {

            List <Server> servers = SystemManager.hydrateServerFromIds(serverIds, user);
            return createConfigActionForServers(user, revisions, servers, type, earliest,
                actionChain);
    }

    /**
     * Creates configuration actions from server-revision maps.
     * @param user the user scheduling actions
     * @param revisions maps servers to multiple revision IDs
     * @param servers a set of server objects
     * @param type the type of configuration action
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled actions
     */
    public static Set<Action> createConfigActionForServers(User user,
        Map<Long, Collection<Long>> revisions, Collection<Server> servers,
        ActionType type, Date earliest, ActionChain actionChain) {
        Set<Action> result = new HashSet<Action>();
        if (actionChain == null) {
            ConfigAction action = ActionManager.createConfigAction(user, type, earliest);
            ActionFactory.save(action);

            for (Server server : servers) {
                ActionManager.checkConfigActionOnServer(type, server);
                ActionFactory.addServerToAction(server.getId(), action);

                ActionManager.addConfigurationRevisionsToAction(user,
                    revisions.get(server.getId()), action, server);
                ActionFactory.save(action);
                result.add(action);
            }
        }
        else {
            int sortOrder = ActionChainFactory.getNextSortOrderValue(actionChain);
            for (Server server : servers) {
                ConfigAction action = ActionManager
                    .createConfigAction(user, type, earliest);
                ActionFactory.save(action);
                result.add(action);
                ActionManager.checkConfigActionOnServer(type, server);
                ActionChainFactory.queueActionChainEntry(action, actionChain, server,
                    sortOrder);
                ActionManager.addConfigurationRevisionsToAction(user,
                    revisions.get(server.getId()), action, server);
                ActionFactory.save(action);
                result.add(action);
            }
        }
        return result;
    }

    /**
     * Schedules a reboot action on a server.
     * @param user the user scheduling actions
     * @param server the affected server
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled actions
     */
    public static Action scheduleRebootAction(User user, Server server, Date earliest,
        ActionChain actionChain) {
        Set<Long> serverIds = new HashSet<Long>();
        serverIds.add(server.getId());
        Set<Action> actions = scheduleRebootActions(user, serverIds, earliest, actionChain);
        return actions.iterator().next();
    }

    /**
     * Schedules one or more reboot actions on multiple servers.
     * @param user the user scheduling actions
     * @param serverIds the affected servers' IDs
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled actions
     */
    public static Set<Action> scheduleRebootActions(User user, Set<Long> serverIds,
        Date earliest, ActionChain actionChain) {
        return scheduleActions(user, ActionFactory.TYPE_REBOOT,
            ActionFactory.TYPE_REBOOT.getName(), earliest, actionChain, null, serverIds);
    }

    /**
     * Schedules an Errata update on a server.
     * @param user the user scheduling actions
     * @param server the affected server
     * @param errataIds a list of erratas IDs
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled actions
     */
    public static Action scheduleErrataUpdate(User user, Server server,
            List<Integer> errataIds, Date earliest, ActionChain actionChain) {
        Set<Long> serverIds = new HashSet<Long>();
        serverIds.add(server.getId());
        Set<Action> actions = scheduleErrataUpdates(user, serverIds, errataIds, earliest,
                actionChain);
        return actions.iterator().next();
    }

    /**
     * Schedules one or more Errata updates on multiple servers.
     * @param user the user scheduling actions
     * @param serverIds the affected servers' IDs
     * @param errataIds a list of erratas IDs
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled actions
     */
    public static Set<Action> scheduleErrataUpdates(User user, Set<Long> serverIds,
        List<Integer> errataIds, Date earliest, ActionChain actionChain) {

        Set<Action> actions = new HashSet<Action>();

        for (Integer errataId : errataIds) {
            Errata currErrata = ErrataManager.lookupErrata(Long.valueOf(errataId), user);
            Action update = ActionManager.createErrataAction(user, currErrata);
            ActionManager.storeAction(update);
            int sortOrder = ActionChainFactory.getNextSortOrderValue(actionChain);

            for (Long serverId : serverIds) {
                ActionChainFactory.queueActionChainEntry(update, actionChain,
                    serverId, sortOrder);
            }
            actions.add(update);
        }

        return actions;
    }

    /**
     * Schedules one or more package installation actions on one or more servers.
     * @param user the user scheduling actions
     * @param serverIds the affected servers' IDs
     * @param packages a list of "package maps"
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled actions
     * @see ActionManager#addPackageActionDetails(Action, List) for "package map"
     */
    public static List<Action> schedulePackageInstalls(User user,
        Collection<Long> serverIds, List<Map<String, Long>> packages, Date earliest,
        ActionChain actionChain) {

        return schedulePackageActionsByOs(user, serverIds, packages, earliest, actionChain,
                ActionFactory.TYPE_PACKAGES_UPDATE);
    }

    /**
     * Schedules one or more package removal actions on one or more servers.
     * @param user the user scheduling actions
     * @param serverIds the affected servers' IDs
     * @param packages a list of "package maps"
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @return scheduled actions
     * @see ActionManager#addPackageActionDetails(Action, List) for "package map"
     */
    public static List<Action> schedulePackageRemovals(User user,
        Collection<Long> serverIds, List<Map<String, Long>> packages, Date earliest,
        ActionChain actionChain) {
        return schedulePackageActionsByOs(user, serverIds, packages, earliest, actionChain,
                ActionFactory.TYPE_PACKAGES_REMOVE);
    }

    /**
     * Schedules generic actions on multiple servers.
     * @param user the user scheduling actions
     * @param type the type
     * @param name the name
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @param sortOrder the sort order or null
     * @param serverIds the affected servers' IDs
     * @return scheduled actions
     * @see com.redhat.rhn.manager.action.ActionManager#scheduleAction
     */
    private static Set<Action> scheduleActions(User user, ActionType type, String name,
        Date earliest, ActionChain actionChain, Integer sortOrder, Set<Long> serverIds) {
        Set<Action> result = new HashSet<Action>();

        if (actionChain == null) {
            Action action = ActionManager.createAction(user, type, name, earliest);
            ActionManager.scheduleForExecution(action, serverIds);
            result.add(action);
        }
        else {
            Integer nextSortOrder = sortOrder;
            if (sortOrder == null) {
                nextSortOrder = ActionChainFactory.getNextSortOrderValue(actionChain);
            }
            for (Long serverId : serverIds) {
                Action action = ActionManager.createAction(user, type, name, earliest);
                ActionChainFactory.queueActionChainEntry(action, actionChain, serverId,
                    nextSortOrder);
                result.add(action);
            }
        }

        return result;
    }

    /**
     * Schedules package actions.
     * @param user the user scheduling actions
     * @param server the server
     * @param packages the packages
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @param sortOrder the sort order or null
     * @param linuxActionType the action type to apply to Linux servers
     * @return scheduled action
     */
    private static PackageAction schedulePackageActionByOs(User user, Server server,
        List<Map<String, Long>> packages, Date earliest, ActionChain actionChain,
            Integer sortOrder, ActionType linuxActionType) {
        return (PackageAction) schedulePackageAction(user, packages, linuxActionType,
            earliest, actionChain, sortOrder, server);
    }

    /**
     * Schedules package actions.
     * @param user the user scheduling actions
     * @param serverIds the affected servers' IDs
     * @param packages the packages involved
     * @param earliest the earliest execution date
     * @param actionChain the action chain or null
     * @param linuxActionType the action type to apply to Linux servers
     * @return scheduled actions
     */
    private static List<Action> schedulePackageActionsByOs(User user,
        Collection<Long> serverIds, List<Map<String, Long>> packages, Date earliest,
            ActionChain actionChain, ActionType linuxActionType) {

        List<Action> result = new LinkedList<Action>();
        Set<Long> rhelServers = new HashSet<Long>();
        rhelServers.addAll(ServerFactory.listLinuxSystems(serverIds));

        if (!rhelServers.isEmpty()) {
            result.addAll(schedulePackageActions(user, packages, linuxActionType, earliest,
                actionChain, null, rhelServers));
        }

        return result;
    }
}
