package io.cattle.platform.process.common.generic;

import io.cattle.platform.engine.manager.impl.ProcessRecord;
import io.cattle.platform.engine.process.ExitReason;
import io.cattle.platform.engine.process.LaunchConfiguration;
import io.cattle.platform.engine.process.impl.AbstractStatesBasedProcessState;
import io.cattle.platform.engine.process.impl.ProcessExecutionExitException;
import io.cattle.platform.engine.process.impl.ResourceStatesDefinition;
import io.cattle.platform.json.JsonMapper;
import io.cattle.platform.lock.definition.LockDefinition;
import io.cattle.platform.object.ObjectManager;
import io.cattle.platform.object.meta.ObjectMetaDataManager;
import io.cattle.platform.object.util.DataAccessor;
import io.cattle.platform.process.common.lock.ResourceChangeLock;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.jooq.exception.DataChangedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GenericResourceProcessState extends AbstractStatesBasedProcessState {

    private static final Logger log = LoggerFactory.getLogger(GenericResourceProcessState.class);

    Object resource;
    String resourceId;
    ObjectManager objectManager;
    LockDefinition processLock;
    Map<String, Object> data;
    String state;

    public GenericResourceProcessState(JsonMapper jsonMapper, ResourceStatesDefinition stateDef, LaunchConfiguration config, ObjectManager objectManager) {
        super(jsonMapper, stateDef);
        this.objectManager = objectManager;
        this.resource = objectManager.loadResource(config.getResourceType(), config.getResourceId());
        this.processLock = new ResourceChangeLock(config.getResourceType(), config.getResourceId());
        this.resourceId = config.getResourceId();
        this.data = config.getData();
        this.state = lookupState();
    }

    @Override
    public Object getResource() {
        return resource;
    }

    @Override
    public LockDefinition getProcessLock() {
        return processLock;
    }

    protected String lookupState() {
        try {
            if (resource == null) {
                return null;
            }
            return BeanUtils.getProperty(resource, getStatesDefinition().getStateField());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e);
        } catch (InvocationTargetException e) {
            throw new IllegalStateException(e);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    public String getState() {
        return state;
    }

    @Override
    public void reload() {
        resource = objectManager.reload(resource);
    }

    @Override
    protected boolean setState(boolean transitioning, String oldState, String newState) {
        if (resource != null && transitioning && ObjectMetaDataManager.STATE_FIELD.equals(getStatesDefinition().getStateField())) {
            DataAccessor field = DataAccessor.fields(resource).withKey(ObjectMetaDataManager.TRANSITIONING_FIELD);
            DataAccessor message = DataAccessor.fields(resource).withKey(ObjectMetaDataManager.TRANSITIONING_MESSAGE_FIELD);

            for (DataAccessor accessor : new DataAccessor[] {field, message}) {
                if (StringUtils.isNotBlank(accessor.as(String.class))) {
                    accessor.remove();
                }
            }
        }

        reload();
        String resourceState = lookupState();
        if (oldState == null || (!oldState.equals(resourceState) && !newState.equals(resourceState))) {
            return false;
        }

        try {
            Object newResource = objectManager.setFields(resource, getStatesDefinition().getStateField(), newState);
            if (newResource == null) {
                return false;
            } else {
                resource = newResource;
                this.state = lookupState();
                return true;
            }
        } catch (DataChangedException e) {
            return false;
        }
    }

    @Override
    public void applyData(Map<String, Object> data) {
        try {
            resource = objectManager.setFields(resource, data);
        } catch (DataChangedException e) {
            throw new ProcessExecutionExitException(ExitReason.STATE_CHANGED);
        }
    }

    @Override
    protected Map<String, Object> convertMap(Map<Object, Object> data) {
        return objectManager.convertToPropertiesFor(resource, data);
    }

    @Override
    public String getResourceId() {
        return resourceId;
    }

    @Override
    public boolean shouldCancel(ProcessRecord record) {
        if (resource == null) {
            log.error("Resource is null, can't find resource id [{}]", resourceId);
            return true;
        }
        return super.shouldCancel(record);
    }

    @Override
    public Map<String, Object> getData() {
        return data;
    }

}

@@ -2929,24 +2929,43 @@
 newState) %7B%0A
+        reload();%0A%0A
         if (
@@ -3560,34 +3560,16 @@
      %7D%0A%0A
-        reload();%0A
         

Fix where transitioning: error is not removed on next transition