package io.cattle.platform.systemstack.process;

import static io.cattle.platform.core.model.tables.AccountTable.*;
import static io.cattle.platform.core.model.tables.InstanceTable.*;
import static io.cattle.platform.core.model.tables.PhysicalHostTable.*;
import static io.cattle.platform.core.model.tables.ProjectTemplateTable.*;

import io.cattle.platform.core.constants.AccountConstants;
import io.cattle.platform.core.constants.InstanceConstants;
import io.cattle.platform.core.constants.MachineConstants;
import io.cattle.platform.core.constants.NetworkConstants;
import io.cattle.platform.core.dao.NetworkDao;
import io.cattle.platform.core.model.Account;
import io.cattle.platform.core.model.Host;
import io.cattle.platform.core.model.Instance;
import io.cattle.platform.core.model.Network;
import io.cattle.platform.core.model.PhysicalHost;
import io.cattle.platform.core.model.ProjectTemplate;
import io.cattle.platform.deferred.util.DeferredUtils;
import io.cattle.platform.docker.constants.DockerInstanceConstants;
import io.cattle.platform.engine.handler.HandlerResult;
import io.cattle.platform.engine.handler.ProcessPreListener;
import io.cattle.platform.engine.process.ProcessInstance;
import io.cattle.platform.engine.process.ProcessState;
import io.cattle.platform.object.meta.ObjectMetaDataManager;
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.object.util.DataUtils;
import io.cattle.platform.process.common.handler.AbstractObjectProcessLogic;
import io.cattle.platform.systemstack.catalog.CatalogService;
import io.github.ibuildthecloud.gdapi.condition.Condition;
import io.github.ibuildthecloud.gdapi.condition.ConditionType;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

public class EnvironmentUpgrade extends AbstractObjectProcessLogic implements ProcessPreListener {

    @Inject
    SystemStackTrigger systemStackTrigger;
    @Inject
    ResourceMonitor resourceMonitor;
    @Inject
    NetworkDao networkDao;

    @Override
    public String[] getProcessNames() {
        return new String[] { "account.upgrade" };
    }

    @Override
    public HandlerResult handle(ProcessState state, ProcessInstance process) {
        Account env = (Account)state.getResource();
        if (!AccountConstants.PROJECT_KIND.equals(env.getKind())) {
            return null;
        }

        String version = env.getVersion();
        if (AccountConstants.ACCOUNT_VERSION.get().equals(version)) {
            return null;
        }

        checkDefaultNetwork(env);

        assignTemplate(env);

        Network network = getIpSectNetwork(env);
        networkDao.migrateToNetwork(network);

        migrateInstances(network);
        upgradeHosts(env);

        objectManager.reload(env);
        return null;
    }

    protected void migrateInstances(Network network) {
        List<Instance> instances = objectManager.find(Instance.class,
                INSTANCE.REMOVED, null,
                INSTANCE.ACCOUNT_ID, network.getAccountId());
        for (Instance instance : instances) {
            String networkMode = DataAccessor.fieldString(instance, DockerInstanceConstants.FIELD_NETWORK_MODE);
            if ("managed".equals(networkMode)) {
                List<Long> networkIds = DataAccessor.fieldLongList(instance, InstanceConstants.FIELD_NETWORK_IDS);
                if (networkIds != null && networkIds.size() == 1 && !network.getId().equals(networkIds.get(0))) {
                    DataAccessor.setField(instance, InstanceConstants.FIELD_NETWORK_IDS, Arrays.asList(network.getId()));
                    objectManager.persist(instance);
                }
            }
        }
    }

    protected void upgradeHosts(Account account) {
        List<PhysicalHost> phyHosts = objectManager.find(PhysicalHost.class,
                PHYSICAL_HOST.DRIVER, new Condition(ConditionType.NOTNULL),
                PHYSICAL_HOST.REMOVED, null,
                PHYSICAL_HOST.ACCOUNT_ID, account.getId());

        for (PhysicalHost physicalHost : phyHosts) {
            for (Host host : objectManager.children(physicalHost, Host.class)) {
                String driver = DataAccessor.fieldString(host, MachineConstants.FIELD_DRIVER);
                if (!physicalHost.getDriver().equals(driver)) {
                    objectManager.setFields(host,
                            MachineConstants.FIELD_DRIVER, physicalHost.getDriver());
                }
            }
        }
    }

    protected void checkDefaultNetwork(Account account) {
        Map<String, Object> fields = DataUtils.getWritableFields(account);
        boolean changed = fields.remove(AccountConstants.FIELD_DEFAULT_NETWORK_ID) != null;
        if (changed) {
            objectManager.persist(account);
        }
    }

    protected Network getIpSectNetwork(final Account account) {
        resourceMonitor.waitFor(account, new ResourcePredicate<Account>() {
            @Override
            public boolean evaluate(Account obj) {
                return findIpSecNetwork(account) != null;
            }

            @Override
            public String getMessage() {
                return "default network to be create";
            }
        });

        return findIpSecNetwork(account);
    }

    protected Network findIpSecNetwork(Account account) {
        return objectManager.findAny(Network.class,
                ObjectMetaDataManager.KIND_FIELD, NetworkConstants.KIND_CNI,
                ObjectMetaDataManager.REMOVED_FIELD, null);
    }

    protected void assignTemplate(final Account env) {
        if (env.getProjectTemplateId() != null) {
            return;
        }

        final ProjectTemplate template = objectManager.findAny(ProjectTemplate.class,
                PROJECT_TEMPLATE.IS_PUBLIC, true,
                PROJECT_TEMPLATE.REMOVED, null,
                PROJECT_TEMPLATE.NAME, CatalogService.DEFAULT_TEMPLATE.get());

        if (template == null) {
            throw new IllegalStateException("Failed to find default template for upgrade");
        }

        DeferredUtils.nest(new Runnable() {
            @Override
            public void run() {
                systemStackTrigger.trigger(env.getId());
                objectManager.setFields(env, ACCOUNT.PROJECT_TEMPLATE_ID, template.getId());
            }
        });
    }

}

@@ -5501,32 +5501,102 @@
 (Network.class,%0A
+                ObjectMetaDataManager.ACCOUNT_FIELD, account.getId(),%0A
                 

Fix network migration w/ multiple accounts