package com.bcxin.api.controllers;

import cn.myapps.common.model.task.Task;
import cn.myapps.designtime.common.cache.DesignTimeSerializableCache;
import cn.myapps.designtime.common.service.DesignTimeServiceManager;
import cn.myapps.designtime.task.service.TaskDesignTimeService;
import cn.myapps.runtime.common.service.RunTimeServiceManager;
import cn.myapps.runtime.scheduler.ejb.TriggerProcess;
import cn.myapps.runtime.scheduler.ejb.TriggerVO;
import cn.myapps.runtime.scheduler.engine.job.BpmPlatformWorkFlowEventJob;
import cn.myapps.runtime.scheduler.engine.job.IscriptTaskJob;
import cn.myapps.runtime.scheduler.engine.job.Job;
import com.bcxin.dtos.TriggerVOWrapper;
import com.bcxin.responses.TriggerDefinitionResponse;
import com.bcxin.runtime.apis.controllers.ControllerAbstract;
import com.bcxin.saas.core.components.DistributedCacheProvider;
import com.bcxin.saas.core.exceptions.SaasBadException;
import com.bcxin.saas.core.utils.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

@RestController("v2_script_execute_controller")
@RequestMapping("/api/v2/runtime/definitions/apps/{appId}/")
public class ScriptExecuteController extends ControllerAbstract {
    private static final Logger logger = LoggerFactory.getLogger(ScriptExecuteController.class);
    private static Map<String,TriggerVO> _toBeUpdatedTriggers = new ConcurrentHashMap<>();
    private final DistributedCacheProvider distributedCacheProvider;

    public ScriptExecuteController(DistributedCacheProvider distributedCacheProvider) {
        this.distributedCacheProvider = distributedCacheProvider;
    }

    @PostMapping("/triggers/{id}/execute")
    public ResponseEntity execute(@PathVariable String appId, @PathVariable String id) throws Exception {
        AtomicBoolean updateFlag = new AtomicBoolean(false);
        String result = null;
        try {
            TriggerProcess triggerProcess = RunTimeServiceManager.triggerProcess(appId);
            TriggerVOWrapper wrapper = distributedCacheProvider.get(getTriggerCacheKey(appId, id), () -> {
                try {
                    if (!CollectionUtils.isEmpty(_toBeUpdatedTriggers)) {
                        TriggerVO triggerVO = _toBeUpdatedTriggers.get(getTriggerCacheKey(appId, id));

                        if (triggerVO != null) {
                            triggerProcess.doUpdateStateById(triggerVO);

                            updateFlag.set(true);
                        }
                    }

                    TriggerVO tmpTrigger = (TriggerVO) triggerProcess.doView(id);

                    return TriggerVOWrapper.create(tmpTrigger, false);
                } catch (Exception ex) {
                    throw new SaasBadException(ex);
                }
            }, 10 * 60);

            if (wrapper == null) {
                return notFound(String.format("Task not found(%s)message", id));
            }

            if (wrapper.getTriggerVO() == null || wrapper.isDeleted()) {
                return notFound(String.format("Task may be deleted(%s)", id));
            }

            TriggerVO trigger = wrapper.getTriggerVO();
            if (TriggerVO.STATE_STOP.equals(trigger.getState())) {
                return ok("Task Execution is stop");
            }

            this.execute(trigger, triggerProcess);

            result = String.format("id=%s;times=%s;state=%s;lastModifyDate=%s;deadline=%s;",
                    trigger.getId(), trigger.getRunTimes(), trigger.getState(),
                    trigger.getLastModifyDate(), trigger.getDeadline());
        } catch (Exception ex) {
            return badRequest(ExceptionUtils.getStackMessage(ex));
        }

        return ok(String.format("Task Execution is completed: changed =%s;re=%s;", updateFlag.get(), result));
    }

    @GetMapping("/triggers")
    public ResponseEntity<Collection<TriggerDefinitionResponse>> search(@PathVariable String appId) throws Exception {
        Collection<TriggerDefinitionResponse> jobScriptResponses =
                distributedCacheProvider.get(
                        DesignTimeSerializableCache.getWrapperKey(String.format("com:app:%s:triggers", appId)),
                        () -> {
                            try {
                                TriggerProcess triggerProcess = RunTimeServiceManager.triggerProcess(appId);
                                Collection<TriggerVO> list = triggerProcess.getStandbyTriggers(appId);
                                Collection<TriggerDefinitionResponse> jobs =
                                        list.stream().map(ii ->
                                                        TriggerDefinitionResponse.create(ii.getApplicationId(), ii.getId(), ii.getTaskId()))
                                                .collect(Collectors.toList());

                                return jobs;
                            } catch (Exception ex) {
                                throw new SaasBadException(ex);
                            }
                        }, 10 * 60);

        logger.error("获取定时任务列表:{}-{}", appId, jobScriptResponses.stream().map(ii -> ii.getTaskId()).collect(Collectors.joining(";")));
        return ok(jobScriptResponses);
    }

    private void execute(TriggerVO trigger, TriggerProcess process) throws Exception {
        /**
         * 不为空表示非取消
         */
        if (trigger != null) {
            Job job = trigger.getJob();
            if (job instanceof BpmPlatformWorkFlowEventJob) {
                BpmPlatformWorkFlowEventJob bpmPlatformWorkFlowEventJob = (BpmPlatformWorkFlowEventJob) job;
                bpmPlatformWorkFlowEventJob.setDoc(trigger.getDoc());
                bpmPlatformWorkFlowEventJob.execute();
            } else if (job instanceof IscriptTaskJob) {
                IscriptTaskJob iscriptTaskJob = (IscriptTaskJob) job;
                iscriptTaskJob.setApplicationId(trigger.getApplicationId());
                iscriptTaskJob.execute();
            } else {
                job.execute();
            }

            boolean deleted = false;
            try {
                Date lastExecuteTime = new Date();
                if (!trigger.isLoop()) {
                    //如果不是脚本任务的话,执行一次就删除就可以了,脚本任务不重复的话就执行一次将状态设为stop
                    if (trigger.getJobType() != TriggerVO.JOB_TYPE_ISCRITP_TASK) {
                        process.doRemove(trigger.getId());
                        deleted = true;
                    } else {
                        trigger.setState(TriggerVO.STATE_STOP);
                        trigger.setRunTimes(trigger.getRunTimes() + 1);

                        _toBeUpdatedTriggers.put(getTriggerCacheKey(trigger.getApplicationId(), trigger.getId()), trigger);
                    }
                } else {
                    trigger.setState(TriggerVO.STATE_WAITING);
                    trigger.setRunTimes(trigger.getRunTimes() + 1);
                    TaskDesignTimeService tp = DesignTimeServiceManager.taskDesignTimeService();
                    IscriptTaskJob iscriptTaskJob = (IscriptTaskJob) job;
                    Task task = (Task) tp.findById(iscriptTaskJob.getTaskId());
                    if (task != null) {
                        long nextExecuteTime = task.getNextExecuteTime(lastExecuteTime);
                        trigger.setDeadline(nextExecuteTime);
                    }

                    _toBeUpdatedTriggers.put(getTriggerCacheKey(trigger.getApplicationId(), trigger.getId()), trigger);
                    // 刷新下次执行时间
                    //process.doUpdateStateById(trigger);
                }
            } finally {
                this.distributedCacheProvider.put(getTriggerCacheKey(trigger.getApplicationId(), trigger.getId()),
                        TriggerVOWrapper.create(trigger, deleted),
                        2 * 60 * 60);
            }
        }
    }

    private String getTriggerCacheKey(String appId, String id) {
        return DesignTimeSerializableCache.getDynamicKey(
                String.format("se:%s:trigger:%s", appId, id)
        );
    }
}
