/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

package org.kie.server.client.admin.impl;

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

import org.kie.server.api.commands.CommandScript;
import org.kie.server.api.commands.DescriptorCommand;
import org.kie.server.api.model.KieServerCommand;
import org.kie.server.api.model.ServiceResponse;
import org.kie.server.api.model.admin.ExecutionErrorInstance;
import org.kie.server.api.model.admin.ExecutionErrorInstanceList;
import org.kie.server.api.model.admin.MigrationReportInstance;
import org.kie.server.api.model.admin.MigrationReportInstanceList;
import org.kie.server.api.model.admin.ProcessNode;
import org.kie.server.api.model.admin.ProcessNodeList;
import org.kie.server.api.model.admin.TimerInstance;
import org.kie.server.api.model.admin.TimerInstanceList;
import org.kie.server.api.model.instance.NodeInstance;
import org.kie.server.api.model.instance.NodeInstanceList;
import org.kie.server.client.KieServicesConfiguration;
import org.kie.server.client.admin.ProcessAdminServicesClient;
import org.kie.server.client.impl.AbstractKieServicesClientImpl;

import static org.kie.server.api.rest.RestURI.*;

public class ProcessAdminServicesClientImpl extends AbstractKieServicesClientImpl implements ProcessAdminServicesClient {

    public ProcessAdminServicesClientImpl(KieServicesConfiguration config) {
        super(config);
    }

    public ProcessAdminServicesClientImpl(KieServicesConfiguration config, ClassLoader classLoader) {
        super(config, classLoader);
    }

    @Override
    public MigrationReportInstance migrateProcessInstance(String containerId, Long processInstanceId, String targetContainerId, String targetProcessId) {
        return migrateProcessInstance(containerId, processInstanceId, targetContainerId, targetProcessId, new HashMap<String, String>());
    }

    @Override
    public MigrationReportInstance migrateProcessInstance(String containerId, Long processInstanceId, String targetContainerId, String targetProcessId, Map<String, String> nodeMapping) {
        MigrationReportInstance reportInstance = null;
        if( config.isRest() ) {
            Map<String, Object> valuesMap = new HashMap<String, Object>();
            valuesMap.put(CONTAINER_ID, containerId);
            valuesMap.put(PROCESS_INST_ID, processInstanceId);

            Map<String, String> headers = new HashMap<String, String>();

            String queryString = "?targetContainerId=" + targetContainerId + "&targetProcessId=" + targetProcessId;

            reportInstance = makeHttpPutRequestAndCreateCustomResponse(
                    build(loadBalancer.getUrl(), ADMIN_PROCESS_URI + "/" + MIGRATE_PROCESS_INST_PUT_URI, valuesMap) + queryString, nodeMapping, MigrationReportInstance.class, headers);
        } else {
            CommandScript script = new CommandScript( Collections.singletonList(
                    (KieServerCommand) new DescriptorCommand( "ProcessAdminService", "migrateProcessInstance", serialize(safeMap(nodeMapping)), marshaller.getFormat().getType(), new Object[]{containerId, processInstanceId, targetContainerId, targetProcessId})));
            ServiceResponse<MigrationReportInstance> response = (ServiceResponse<MigrationReportInstance>) executeJmsCommand( script, DescriptorCommand.class.getName(), "BPM", containerId ).getResponses().get(0);
            throwExceptionOnFailure(response);
            if (shouldReturnWithNullResponse(response)) {
                return null;
            }
            reportInstance = response.getResult();
        }

        return reportInstance;
    }

    @Override
    public List<MigrationReportInstance> migrateProcessInstances(String containerId, List<Long> processInstancesId, String targetContainerId, String targetProcessId) {
        return migrateProcessInstances(containerId, processInstancesId, targetContainerId, targetProcessId, new HashMap<String, String>());
    }

    @Override
    public List<MigrationReportInstance> migrateProcessInstances(String containerId, List<Long> processInstancesId, String targetContainerId, String targetProcessId, Map<String, String> nodeMapping) {
        MigrationReportInstanceList reportInstanceList = null;
        if( config.isRest() ) {
            Map<String, Object> valuesMap = new HashMap<String, Object>();
            valuesMap.put(CONTAINER_ID, containerId);

            Map<String, String> headers = new HashMap<String, String>();
            String queryStringBase = buildQueryString(PROCESS_INST_ID, processInstancesId);
            String queryString = queryStringBase + "&targetContainerId=" + targetContainerId + "&targetProcessId=" + targetProcessId;

            reportInstanceList = makeHttpPutRequestAndCreateCustomResponse(
                    build(loadBalancer.getUrl(), ADMIN_PROCESS_URI + "/" + MIGRATE_PROCESS_INSTANCES_PUT_URI, valuesMap) + queryString, nodeMapping, MigrationReportInstanceList.class, headers);
        } else {
            CommandScript script = new CommandScript( Collections.singletonList(
                    (KieServerCommand) new DescriptorCommand( "ProcessAdminService", "migrateProcessInstances", serialize(safeMap(nodeMapping)), marshaller.getFormat().getType(), new Object[]{containerId, processInstancesId, targetContainerId, targetProcessId})));
            ServiceResponse<MigrationReportInstanceList> response = (ServiceResponse<MigrationReportInstanceList>) executeJmsCommand( script, DescriptorCommand.class.getName(), "BPM", containerId ).getResponses().get(0);
            throwExceptionOnFailure(response);
            if (shouldReturnWithNullResponse(response)) {
                return null;
            }
            reportInstanceList = response.getResult();
        }
        if (reportInstanceList != null) {
            return reportInstanceList.getItems();
        }
        return Collections.emptyList();
    }

    @Override
    public List<ProcessNode> getProcessNodes(String containerId, Long processInstanceId) {
        ProcessNodeList result = null;
        if( config.isRest() ) {
            Map<String, Object> valuesMap = new HashMap<String, Object>();
            valuesMap.put(CONTAINER_ID, containerId);
            valuesMap.put(PROCESS_INST_ID, processInstanceId);

            result = makeHttpGetRequestAndCreateCustomResponse(
                    build(loadBalancer.getUrl(), ADMIN_PROCESS_URI + "/" + NODES_PROCESS_INST_GET_URI, valuesMap), ProcessNodeList.class);

        } else {
            CommandScript script = new CommandScript( Collections.singletonList( (KieServerCommand)
                    new DescriptorCommand( "ProcessAdminService", "getProcessNodes", new Object[]{containerId, processInstanceId}) ) );
            ServiceResponse<ProcessNodeList> response = (ServiceResponse<ProcessNodeList>) executeJmsCommand( script, DescriptorCommand.class.getName(), "BPM" ).getResponses().get(0);

            throwExceptionOnFailure(response);
            if (shouldReturnWithNullResponse(response)) {
                return null;
            }
            result = response.getResult();
        }

        if (result != null && result.getItems() != null) {
            return result.getItems();
        }

        return Collections.emptyList();
    }

    @Override
    public void cancelNodeInstance(String containerId, Long processInstanceId, Long nodeInstanceId) {
        if( config.isRest() ) {
            Map<String, Object> valuesMap = new HashMap<String, Object>();
            valuesMap.put(CONTAINER_ID, containerId);
            valuesMap.put(PROCESS_INST_ID, processInstanceId);
            valuesMap.put(NODE_INSTANCE_ID, nodeInstanceId);

            makeHttpDeleteRequestAndCreateCustomResponse(
                    build(loadBalancer.getUrl(), ADMIN_PROCESS_URI + "/" + CANCEL_NODE_INST_PROCESS_INST_DELETE_URI, valuesMap), null);
        } else {
            CommandScript script = new CommandScript( Collections.singletonList(
                    (KieServerCommand) new DescriptorCommand( "ProcessAdminService", "cancelNodeInstance", new Object[]{containerId, processInstanceId, nodeInstanceId})));
            ServiceResponse<?> response = (ServiceResponse<?>) executeJmsCommand( script, DescriptorCommand.class.getName(), "BPM", containerId ).getResponses().get(0);
            throwExceptionOnFailure(response);
        }
    }

    @Override
    public void retriggerNodeInstance(String containerId, Long processInstanceId, Long nodeInstanceId) {

        if( config.isRest() ) {
            Map<String, Object> valuesMap = new HashMap<String, Object>();
            valuesMap.put(CONTAINER_ID, containerId);
            valuesMap.put(PROCESS_INST_ID, processInstanceId);
            valuesMap.put(NODE_INSTANCE_ID, nodeInstanceId);

            Map<String, String> headers = new HashMap<String, String>();


            makeHttpPutRequestAndCreateCustomResponse(
                    build(loadBalancer.getUrl(), ADMIN_PROCESS_URI + "/" + RETRIGGER_NODE_INST_PROCESS_INST_PUT_URI, valuesMap), null, null, headers);
        } else {
            CommandScript script = new CommandScript( Collections.singletonList(
                    (KieServerCommand) new DescriptorCommand( "ProcessAdminService", "retriggerNodeInstance", new Object[]{containerId, processInstanceId, nodeInstanceId})));
            ServiceResponse<?> response = (ServiceResponse<?>) executeJmsCommand( script, DescriptorCommand.class.getName(), "BPM", containerId ).getResponses().get(0);
            throwExceptionOnFailure(response);
        }
    }

    @Override
    public List<NodeInstance> getActiveNodeInstances(String containerId, Long processInstanceId) {
        NodeInstanceList result = null;
        if( config.isRest() ) {
            Map<String, Object> valuesMap = new HashMap<String, Object>();
            valuesMap.put(CONTAINER_ID, containerId);
            valuesMap.put(PROCESS_INST_ID, processInstanceId);

            result = makeHttpGetRequestAndCreateCustomResponse(
                    build(loadBalancer.getUrl(), ADMIN_PROCESS_URI + "/" + NODE_INSTANCES_PROCESS_INST_GET_URI, valuesMap), NodeInstanceList.class);

        } else {
            CommandScript script = new CommandScript( Collections.singletonList( (KieServerCommand)
                    new DescriptorCommand( "ProcessAdminService", "getActiveNodeInstances", new Object[]{containerId, processInstanceId}) ) );
            ServiceResponse<NodeInstanceList> response = (ServiceResponse<NodeInstanceList>) executeJmsCommand( script, DescriptorCommand.class.getName(), "BPM" ).getResponses().get(0);

            throwExceptionOnFailure(response);
            if (shouldReturnWithNullResponse(response)) {
                return null;
            }
            result = response.getResult();
        }

        if (result != null && result.getItems() != null) {
            return result.getItems();
        }

        return Collections.emptyList();
    }

    @Override
    public void updateTimer(String containerId, Long processInstanceId, long timerId, long delay, long period, int repeatLimit) {
        updateTimer(containerId, processInstanceId, timerId, delay, period, repeatLimit, false);
    }

    @Override
    public void updateTimerRelative(String containerId, Long processInstanceId, long timerId, long delay, long period, int repeatLimit) {
        updateTimer(containerId, processInstanceId, timerId, delay, period, repeatLimit, true);
    }

    @Override
    public List<TimerInstance> getTimerInstances(String containerId, Long processInstanceId) {
        TimerInstanceList result = null;
        if( config.isRest() ) {
            Map<String, Object> valuesMap = new HashMap<String, Object>();
            valuesMap.put(CONTAINER_ID, containerId);
            valuesMap.put(PROCESS_INST_ID, processInstanceId);

            result = makeHttpGetRequestAndCreateCustomResponse(
                    build(loadBalancer.getUrl(), ADMIN_PROCESS_URI + "/" + TIMERS_PROCESS_INST_GET_URI, valuesMap), TimerInstanceList.class);

        } else {
            CommandScript script = new CommandScript( Collections.singletonList( (KieServerCommand)
                    new DescriptorCommand( "ProcessAdminService", "getTimerInstances", new Object[]{containerId, processInstanceId}) ) );
            ServiceResponse<TimerInstanceList> response = (ServiceResponse<TimerInstanceList>) executeJmsCommand( script, DescriptorCommand.class.getName(), "BPM" ).getResponses().get(0);

            throwExceptionOnFailure(response);
            if (shouldReturnWithNullResponse(response)) {
                return null;
            }
            result = response.getResult();
        }

        if (result != null && result.getItems() != null) {
            return result.getItems();
        }

        return Collections.emptyList();
    }

    @Override
    public void triggerNode(String containerId, Long processInstanceId, Long nodeId) {
        if( config.isRest() ) {
            Map<String, Object> valuesMap = new HashMap<String, Object>();
            valuesMap.put(CONTAINER_ID, containerId);
            valuesMap.put(PROCESS_INST_ID, processInstanceId);
            valuesMap.put(NODE_ID, nodeId);

            Map<String, String> headers = new HashMap<String, String>();


            makeHttpPostRequestAndCreateCustomResponse(
                    build(loadBalancer.getUrl(), ADMIN_PROCESS_URI + "/" + TRIGGER_NODE_PROCESS_INST_POST_URI, valuesMap), null, null, headers);
        } else {
            CommandScript script = new CommandScript( Collections.singletonList(
                    (KieServerCommand) new DescriptorCommand( "ProcessAdminService", "triggerNode", new Object[]{containerId, processInstanceId, nodeId})));
            ServiceResponse<?> response = (ServiceResponse<?>) executeJmsCommand( script, DescriptorCommand.class.getName(), "BPM", containerId ).getResponses().get(0);
            throwExceptionOnFailure(response);
        }
    }

    @Override
    public void acknowledgeError(String containerId, String... errorId) {
        ArrayList<String> errorIds = new ArrayList<>(Arrays.asList(errorId));
        if( config.isRest() ) {
            Map<String, Object> valuesMap = new HashMap<String, Object>();
            valuesMap.put(CONTAINER_ID, containerId);

            Map<String, String> headers = new HashMap<String, String>();

            String endpoint = ACK_ERROR_PUT_URI;
            String queryString = "";
            if (errorIds.size() > 1) {
                endpoint = ACK_ERRORS_PUT_URI;
                queryString = getAdditionalParams(queryString, "errorId", errorIds);
            } else {
                valuesMap.put(ERROR_ID, errorIds.get(0));
            }

            makeHttpPutRequestAndCreateCustomResponse(
                    build(loadBalancer.getUrl(), ADMIN_PROCESS_URI + "/" + endpoint, valuesMap) + queryString, "", String.class, headers);
        } else {

            CommandScript script = new CommandScript( Collections.singletonList(
                    (KieServerCommand) new DescriptorCommand( "ProcessAdminService", "acknowledgeError", new Object[]{containerId, safeList(errorIds)})));
            ServiceResponse<?> response = (ServiceResponse<?>) executeJmsCommand( script, DescriptorCommand.class.getName(), "BPM", containerId ).getResponses().get(0);
            throwExceptionOnFailure(response);
        }
    }

    @Override
    public ExecutionErrorInstance getError(String containerId, String errorId) {
        ExecutionErrorInstance result = null;
        if( config.isRest() ) {
            Map<String, Object> valuesMap = new HashMap<String, Object>();
            valuesMap.put(CONTAINER_ID, containerId);
            valuesMap.put(ERROR_ID, errorId);

            result = makeHttpGetRequestAndCreateCustomResponse(
                    build(loadBalancer.getUrl(), ADMIN_PROCESS_URI + "/" + ERROR_GET_URI , valuesMap), ExecutionErrorInstance.class);

        } else {
            CommandScript script = new CommandScript( Collections.singletonList( (KieServerCommand)
                    new DescriptorCommand( "ProcessAdminService", "getError", new Object[]{containerId, errorId}) ) );
            ServiceResponse<ExecutionErrorInstance> response = (ServiceResponse<ExecutionErrorInstance>) executeJmsCommand( script, DescriptorCommand.class.getName(), "BPM" ).getResponses().get(0);

            throwExceptionOnFailure(response);
            if (shouldReturnWithNullResponse(response)) {
                return null;
            }
            result = response.getResult();
        }

        return result;
    }

    @Override
    public List<ExecutionErrorInstance> getErrors(String containerId, boolean includeAcknowledged, Integer page, Integer pageSize) {
        ExecutionErrorInstanceList result = null;
        if( config.isRest() ) {
            Map<String, Object> valuesMap = new HashMap<String, Object>();
            valuesMap.put(CONTAINER_ID, containerId);

            String queryString = "?includeAck=" + includeAcknowledged;
            queryString = getPagingQueryString(queryString, page, pageSize);

            result = makeHttpGetRequestAndCreateCustomResponse(
                    build(loadBalancer.getUrl(), ADMIN_PROCESS_URI + "/" + ERRORS_GET_URI + queryString, valuesMap), ExecutionErrorInstanceList.class);

        } else {
            CommandScript script = new CommandScript( Collections.singletonList( (KieServerCommand)
                    new DescriptorCommand( "ProcessAdminService", "getExecutionErrors", new Object[]{containerId, includeAcknowledged, page, pageSize, "", true}) ) );
            ServiceResponse<ExecutionErrorInstanceList> response = (ServiceResponse<ExecutionErrorInstanceList>) executeJmsCommand( script, DescriptorCommand.class.getName(), "BPM" ).getResponses().get(0);

            throwExceptionOnFailure(response);
            if (shouldReturnWithNullResponse(response)) {
                return null;
            }
            result = response.getResult();
        }

        if (result != null && result.getItems() != null) {
            return result.getItems();
        }

        return Collections.emptyList();
    }

    @Override
    public List<ExecutionErrorInstance> getErrorsByProcessInstance(String containerId, Long processInstanceId, boolean includeAcknowledged, Integer page, Integer pageSize) {
        return getErrorsByProcessInstanceAndNode(containerId, processInstanceId, "", includeAcknowledged, page, pageSize);
    }

    @Override
    public List<ExecutionErrorInstance> getErrorsByProcessInstanceAndNode(String containerId, Long processInstanceId, String nodeName, boolean includeAcknowledged, Integer page, Integer pageSize) {
        ExecutionErrorInstanceList result = null;
        if( config.isRest() ) {
            Map<String, Object> valuesMap = new HashMap<String, Object>();
            valuesMap.put(CONTAINER_ID, containerId);
            valuesMap.put(PROCESS_INST_ID, processInstanceId);

            String queryString = "?includeAck=" + includeAcknowledged +"&node=" + nodeName;
            queryString = getPagingQueryString(queryString, page, pageSize);

            result = makeHttpGetRequestAndCreateCustomResponse(
                    build(loadBalancer.getUrl(), ADMIN_PROCESS_URI + "/" + ERRORS_BY_PROCESS_INST_GET_URI + queryString, valuesMap), ExecutionErrorInstanceList.class);

        } else {
            CommandScript script = new CommandScript( Collections.singletonList( (KieServerCommand)
                    new DescriptorCommand( "ProcessAdminService", "getExecutionErrorsByProcessInstance", new Object[]{containerId, processInstanceId, nodeName, includeAcknowledged, page, pageSize, "", true}) ) );
            ServiceResponse<ExecutionErrorInstanceList> response = (ServiceResponse<ExecutionErrorInstanceList>) executeJmsCommand( script, DescriptorCommand.class.getName(), "BPM" ).getResponses().get(0);

            throwExceptionOnFailure(response);
            if (shouldReturnWithNullResponse(response)) {
                return null;
            }
            result = response.getResult();
        }

        if (result != null && result.getItems() != null) {
            return result.getItems();
        }

        return Collections.emptyList();
    }

    /*
     * helper methods
     */
    protected void updateTimer(String containerId, Long processInstanceId, long timerId, long delay, long period, int repeatLimit, boolean relative) {
        Map<String, Number> timerUpdate = new HashMap<>();
        timerUpdate.put("delay", delay);
        timerUpdate.put("period", period);
        timerUpdate.put("repeatLimit", repeatLimit);

        if( config.isRest() ) {
            Map<String, Object> valuesMap = new HashMap<String, Object>();
            valuesMap.put(CONTAINER_ID, containerId);
            valuesMap.put(PROCESS_INST_ID, processInstanceId);
            valuesMap.put(TIMER_INSTANCE_ID, timerId);

            Map<String, String> headers = new HashMap<String, String>();


            makeHttpPutRequestAndCreateCustomResponse(
                    build(loadBalancer.getUrl(), ADMIN_PROCESS_URI + "/" + UPDATE_TIMER_PROCESS_INST_PUT_URI, valuesMap)+ "?relative=" + relative, timerUpdate, null, headers);
        } else {
            CommandScript script = new CommandScript( Collections.singletonList(
                    (KieServerCommand) new DescriptorCommand( "ProcessAdminService", "updateTimer", serialize(timerUpdate), marshaller.getFormat().getType(), new Object[]{containerId, processInstanceId, timerId, relative})));
            ServiceResponse<?> response = (ServiceResponse<?>) executeJmsCommand( script, DescriptorCommand.class.getName(), "BPM", containerId ).getResponses().get(0);
            throwExceptionOnFailure(response);
        }
    }
}
