package io.cattle.platform.process.externalevent;

import io.cattle.platform.allocator.constraint.HostAffinityConstraint;
import io.cattle.platform.allocator.service.AllocationHelper;
import io.cattle.platform.core.constants.ExternalEventConstants;
import io.cattle.platform.core.constants.InstanceConstants;
import io.cattle.platform.core.dao.InstanceDao;
import io.cattle.platform.core.model.ExternalEvent;
import io.cattle.platform.core.model.Host;
import io.cattle.platform.core.model.Instance;
import io.cattle.platform.engine.handler.HandlerResult;
import io.cattle.platform.engine.process.ProcessInstance;
import io.cattle.platform.engine.process.ProcessState;
import io.cattle.platform.engine.process.impl.ProcessCancelException;
import io.cattle.platform.object.process.ObjectProcessManager;
import io.cattle.platform.object.resource.ResourceMonitor;
import io.cattle.platform.object.resource.ResourcePredicate;
import io.cattle.platform.object.util.DataAccessor;
import io.cattle.platform.process.common.handler.AbstractObjectProcessHandler;
import io.cattle.platform.process.common.util.ProcessUtils;

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

import javax.inject.Inject;
import javax.inject.Named;

import org.apache.commons.lang3.StringUtils;

@Named
public class ExternalHostEventCreate extends AbstractObjectProcessHandler {

    @Inject
    AllocationHelper allocationHelper;

    @Inject
    InstanceDao instanceDao;

    @Inject
    ObjectProcessManager processManager;

    @Inject
    ResourceMonitor resourceMonitor;

    @Override
    public HandlerResult handle(ProcessState state, ProcessInstance process) {
        ExternalEvent event = (ExternalEvent)state.getResource();

        if (!ExternalEventConstants.KIND_EXTERNAL_HOST_EVENT.equals(event.getKind()) ||
                !ExternalEventConstants.TYPE_HOST_EVACUATE.equals(event.getEventType())) {
            return null;
        }

        boolean delete = DataAccessor.fieldBool(event, ExternalEventConstants.FIELD_DELETE_HOST);
        List<Long> hosts = getHosts(event);

        for (long hostId : hosts) {
            Host host = objectManager.loadResource(Host.class, hostId);
            if (host == null) {
                continue;
            }

            try {
                deactivateHost(state, host);
            } catch (ProcessCancelException e) {
            }

            if (delete) {
                host = objectManager.reload(host);
                try {
                    remove(host, state.getData());
                } catch (ProcessCancelException e) {
                }

                host = objectManager.reload(host);
                purge(host, state.getData());
            }
        }

        return null;
    }

    protected List<Long> getHosts(ExternalEvent event) {
        List<Long> hosts = new ArrayList<>();

        String label = DataAccessor.fieldString(event, ExternalEventConstants.FIELD_HOST_LABEL);
        if (StringUtils.isNotBlank(label)) {
            Map<String, String> labels = new HashMap<>();
            labels.put(HostAffinityConstraint.LABEL_HEADER_AFFINITY_HOST_LABEL, DataAccessor.fieldString(event, ExternalEventConstants.FIELD_HOST_LABEL));
            hosts.addAll(allocationHelper.getAllHostsSatisfyingHostAffinity(event.getAccountId(), labels));
        }

        Long hostId = DataAccessor.fieldLong(event, ExternalEventConstants.FIELD_HOST_ID);
        if (hostId != null) {
            hosts.add(hostId);
        }

        return hosts;
    }

    protected void deactivateHost(ProcessState state, Host host) {
        deactivate(host, null);

        List<? extends Instance> instances = instanceDao.getNonRemovedInstanceOn(host.getId());
        List<Instance> removed = new ArrayList<>();
        for (Instance instance : instances ) {
            if (InstanceConstants.isSystem(instance)) {
                continue;
            }
            try {
                processManager.scheduleProcessInstanceAsync(InstanceConstants.PROCESS_STOP, instance,
                        ProcessUtils.chainInData(new HashMap<String, Object>(), InstanceConstants.PROCESS_STOP,
                                InstanceConstants.PROCESS_REMOVE));
            } catch (ProcessCancelException e) {
            }

            removed.add(instance);
        }

        for (Instance instance : removed) {
            resourceMonitor.waitFor(instance, new ResourcePredicate<Instance>() {
                @Override
                public boolean evaluate(Instance obj) {
                    return obj.getRemoved() != null;
                }

                @Override
                public String getMessage() {
                    return "removed";
                }
            });
        }
    }

    @Override
    public String[] getProcessNames() {
        return new String[] {ExternalEventConstants.PROCESS_EXTERNAL_EVENT_CREATE};
    }

}