package com.bcxin.services.impls;

import cn.myapps.common.auth.IUser;
import cn.myapps.common.controller.ErrorMessage;
import cn.myapps.common.controller.Resource;
import cn.myapps.common.data.ParamsTable;
import cn.myapps.common.model.activity.Activity;
import cn.myapps.common.model.activity.ActivityParent;
import cn.myapps.common.util.StringUtil;
import cn.myapps.common.util.cache.MemoryCacheUtil;
import cn.myapps.designtime.activity.service.ActivityDesignTimeService;
import cn.myapps.designtime.common.service.DesignTimeServiceManager;
import cn.myapps.designtime.form.service.FormDesignTimeService;
import cn.myapps.designtime.view.service.ViewDesignTimeService;
import cn.myapps.runtime.common.service.RunTimeServiceManager;
import cn.myapps.runtime.dynaform.document.ejb.Document;
import cn.myapps.runtime.dynaform.document.ejb.DocumentProcess;
import cn.myapps.runtime.dynaform.document.ejb.Item;
import cn.myapps.runtime.dynaform.form.ejb.Form;
import cn.myapps.runtime.dynaform.form.ejb.ValidateMessage;
import cn.myapps.runtime.macro.runner.IRunner;
import cn.myapps.runtime.macro.runner.JavaScriptFactory;
import cn.myapps.runtime.macro.runner.JsMessage;
import cn.myapps.runtime.workflow.storage.runtime.ejb.FlowRuntimeService;
import cn.myapps.runtime.workflow.storage.runtime.ejb.FlowRuntimeServiceImpl;
import cn.myapps.runtime.workflow.storage.runtime.ejb.FlowStateRT;
import com.alibaba.fastjson.JSONObject;
import com.bcxin.dtos.ExecuteActionResultDto;
import com.bcxin.enums.ActionStep;
import com.bcxin.saas.core.exceptions.SaasBadException;
import com.bcxin.saas.core.exceptions.SaasNofoundException;
import com.bcxin.services.DocumentService;
import com.bcxin.services.commands.CreatedDocumentCommand;
import com.bcxin.services.commands.results.CreatedDocumentCommandResult;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.Option;
import org.springframework.stereotype.Service;

import java.util.*;

import static com.jayway.jsonpath.JsonPath.parse;

@Service
public class DocumentServiceImpl implements DocumentService {

    @Override
    public CreatedDocumentCommandResult dispatch(CreatedDocumentCommand command) {
        DocumentProcess dProcess = null;
        try {
            Document doc = this.prepareDocument(
                    command.getContent(),
                    command.getAppId(),
                    "$.document",
                    command.getParams(),
                    command.getUser());

            Activity act = getSelectedActivity(command.getAppId(), command.getActId(), doc, command.getParams());
            if(act==null) {
                throw new SaasNofoundException("系统异常, 找不到Activity事件");
            }

            ExecuteActionResultDto actionResult = null;
            //步骤一
            ActionStep step = command.getStep();


            dProcess = RunTimeServiceManager.documentProcess(command.getAppId());
            dProcess.beginTransaction();
            if (step.executable(ActionStep.BeforeAction)) {
                actionResult = this.executeCore_runBefore(doc, command.getParams(), command.getUser(), act);
                if (actionResult.getResultType() != ExecuteActionResultDto.ActionResultType.OK) {
                    if (dProcess != null) {
                        dProcess.rollbackTransaction();
                    }

                    return CreatedDocumentCommandResult.result(actionResult, ActionStep.BeforeAction);
                }
//                JSONObject data = actionResult.getData().getData() == null ? null : (JSONObject)actionResult.getData().getData();
                step = act.getType() == 10 ? ActionStep.AfterAction : ActionStep.ActionCore;
            }

            //步骤二
            if (step.executable(ActionStep.ActionCore)) {
                switch (act.getType()) {
                    case 34:
                    case 11:
                        actionResult = this.executeCore_Normal_Button(
                                command.getAppId(), doc, command.getParams(), command.getUser());
                        break;
                    case 13:
                        actionResult = this.executeCore_Custom_Button(command.getAppId(), doc, command.getParams(), command.getUser(), act);
                        break;
                    default:
                        throw new SaasNofoundException("系统异常, 请联系管理员,暂不支持该按钮类型");
                }

                if (actionResult.getResultType() != ExecuteActionResultDto.ActionResultType.OK) {
                    if (dProcess != null) {
                        dProcess.rollbackTransaction();
                    }

                    return CreatedDocumentCommandResult.result(actionResult, ActionStep.ActionCore);
                }
                step = ActionStep.AfterAction;
            }

            //步骤三
            if (step.executable(ActionStep.AfterAction)) {
                actionResult = this.executeCore_runAfter(command.getParams(), doc, command.getUser(), act);
            }

            if (actionResult.getResultType() != ExecuteActionResultDto.ActionResultType.Error) {
                dProcess.commitTransaction();
            }else {
                //todo: 理论上永远不会执行到
                dProcess.rollbackTransaction();
            }

            return CreatedDocumentCommandResult.result(actionResult, step);
        } catch (Exception ex) {
            try {
                if (dProcess != null) {
                    dProcess.rollbackTransaction();
                }
            } catch (Exception ee) {

            }

            throw new SaasBadException("文档提交失败:" + ex.getMessage());
        }
    }

    private Document prepareDocument(
            String jsonContent, String applicationId, String baseJsonPath,
            ParamsTable params,
            IUser user)
            throws Exception {
        Configuration configuration = Configuration.defaultConfiguration();
        configuration = configuration.addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL);
        DocumentContext parse = parse(jsonContent, configuration);
        String formid = parse.read(baseJsonPath + ".formId");
        String viewid = parse.read(baseJsonPath + ".viewId");
        String stateid = parse.read(baseJsonPath + ".stateId");
        String parentid = parse.read(baseJsonPath + ".parentId");
        String sign = parse.read(baseJsonPath + ".sign");
        String docId = params.getDocId();
        if (StringUtil.isBlank(docId)) {
            docId = parse.read(baseJsonPath + ".id");
        }

        String isRelate = parse.read(baseJsonPath + ".isRelate");
        List subDocuments = parse.read(baseJsonPath + ".subDocuments");
        Map<String, Object> items = parse.read(baseJsonPath + ".items");
        Object delete = parse.read(baseJsonPath + ".delete");
        Object edit = parse.read(baseJsonPath + ".edit");
        Object isFromRefresh = params.getParameter("isFromRefresh");
        Object exparams = parse.read(baseJsonPath + ".exparams");

        if (exparams != null) {
            Map<String, Object> exparamsMap = (Map<String, Object>) exparams;
            for (Map.Entry entry : exparamsMap.entrySet()) {
                String key = entry.getKey().toString();
                Object value = entry.getValue();
                params.setParameter(key, value);
            }
        }

        if (isRelate != null && isRelate.equals("true")) {
            params.setParameter("isRelate", "true");
            if (!StringUtil.isBlank(parentid)) {
                params.setParameter("relateid", parentid);
            }
        } else {
            if (!StringUtil.isBlank(parentid)) {
                params.setParameter("relateid", parentid);
            }
            parentid = "";
        }

        if (parentid != null && !parentid.isEmpty())
            params.setParameter("parentid", parentid);
        if (viewid != null && !viewid.isEmpty())
            params.setParameter("viewid", viewid);

        DocumentProcess dProcess = RunTimeServiceManager.documentProcess(applicationId);
        Form form = null;
        Document doc = null;
        if (items != null) {
            /**
             * 审批流程过程中很多操作步骤都是通过这个缓存来修复的.
             */
            Document temp = (Document) dProcess.doView(docId);
            if (temp == null) {
                form = DesignTimeServiceManager.formDesignTimeService().doView(formid);
                temp = dProcess.doNew(form, user, params);
                temp.setId(docId);
            }

            for (Map.Entry<String, Object> entry : items.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                Item item = temp.findItem(key);

                if (item == null) {
                    if (!StringUtil.isBlank(key) && key.lastIndexOf("_show") > -1) {
                        item = temp.findItem(key.substring(0, key.lastIndexOf("_show")));
                    }
                }
                if (item != null) {
                    params.setParameter(key, value);
                    item.setValue(value);
                }
            }

            doc = temp;
        } else {
            return new Document();
        }

        if (!items.isEmpty() && isFromRefresh == null) {
            if (form == null) {
                form = DesignTimeServiceManager.formDesignTimeService().doView(formid);
            }

            doc = form.createDocument(doc, params, user);
        }

        if (!StringUtil.isBlank(parentid)) {
            doc.setParent(parentid);
        }
        if (!StringUtil.isBlank(stateid)) {
            doc.setState(stateid);
        }
        if (!StringUtil.isBlank(sign)) {
            doc.setSign(sign);
        }
        if (StringUtil.isBlank(doc.getFormid()) && !StringUtil.isBlank(formid)) {
            doc.setFormid(formid);
        }

        if (StringUtil.isBlank(doc.getApplicationid()) && !StringUtil.isBlank(applicationId)) {
            doc.setApplicationid(applicationId);
        }

        List<Document> subDocumentList = new ArrayList<Document>();
        if (subDocuments != null) {
            for (Iterator iterator = subDocuments.iterator(); iterator.hasNext(); ) {
                Map map = (Map) iterator.next();
                ParamsTable newParams = params.copyContextParams();
                Iterator<Map.Entry<String, Object>> iter = map.entrySet().iterator();
                while (iter.hasNext()) {
                    Map.Entry<String, Object> entry = iter.next();
                    String name = entry.getKey();
                    Object value = entry.getValue();
                    try {
                        if (value instanceof String[])
                            if (((String[]) value).length > 1)
                                newParams.setParameter(name, value);
                            else
                                newParams.setParameter(name, ((String[]) value)[0]);
                        else
                            newParams.setParameter(name, value);
                    } catch (Exception e) {
                        throw e;
                    }
                }

                Document subDoc = prepareDocument(net.sf.json.JSONObject.fromObject(map).toString(), applicationId, "$", newParams, user);
                subDocumentList.add(subDoc);
            }
        }
        doc.setSubDocuments(subDocumentList.isEmpty() ? null : subDocumentList);

        if (delete != null) {
            doc.setDelete(true);
        }
        if (edit != null) {
            doc.setEdit(true);
        }

        return doc;
    }

    private ExecuteActionResultDto executeCore_runBefore(Document doc, ParamsTable params, IUser user, Activity act) throws Exception {
        // 运行前脚本
        if (!StringUtil.isBlank(act.getBeforeActionScript()) || !StringUtil.isBlank(act.getRetractBeforeActionScript())) {
            // 保存先前doc,以便与执行完脚本的后的doc做比较
            Object result = act.runBeforeActionScript(doc, params, user);
            if (result != null) {
                if (result instanceof String && ((String) result).trim().length() > 0) {
                    result = new JsMessage(JsMessage.TYPE_SUCCESS, (String) result);
                }
            }

            JSONObject jsonObj = new JSONObject();
            if (result != null && result instanceof JsMessage
                    && !StringUtil.isBlank(((JsMessage) result).getContent())) {
                JsMessage jsMessage = (JsMessage) result;
                jsonObj.put("type", ((JsMessage) result).getType());
                jsonObj.put("content", ((JsMessage) result).getContent());
                jsonObj.put("step", ActionStep.BeforeAction);
                if(jsMessage.getType()==JsMessage.TYPE_CONFIRM) {
                    jsonObj.put("next-step", ActionStep.ActionCore);
                }else {
                    jsonObj.put("next-step", ActionStep.BeforeAction);
                }

                return ExecuteActionResultDto.create(ExecuteActionResultDto.ActionResultType.Message, Resource.ok(jsonObj));
            } else {
                //当执行成功之后, 将Document推入缓存中
                //MemoryCacheUtil.putToPrivateSpace(doc.getId(), doc, user);
                return ExecuteActionResultDto.create(ExecuteActionResultDto.ActionResultType.OK, Resource.ok(jsonObj,ActionStep.BeforeAction.name()));
            }
        } else {
            //当无执行前脚本的时候, 将数据加入缓存中
            //MemoryCacheUtil.putToPrivateSpace(doc.getId(), doc, user);
        }

        return ExecuteActionResultDto.create(ExecuteActionResultDto.ActionResultType.OK, Resource.ok(null,ActionStep.BeforeAction.name()));
    }

    private ExecuteActionResultDto executeCore_Normal_Button(String applicationId,Document doc, ParamsTable params, IUser user) throws Exception {
        DocumentProcess dProcess = RunTimeServiceManager.documentProcess(applicationId);
        Collection<Document> subDocuments = doc.getFrontSubDocuments();
        Collection<ErrorMessage> errors = validateDocument(params, doc, applicationId, user);

        if (errors.isEmpty()) {
            if (subDocuments != null) {
                List<Document> copySubDocuments = new ArrayList<Document>();
                copySubDocuments.addAll(doc.getSubDocuments());
                for (Document document : copySubDocuments) {
                    //保存子表
                    if (document.isDelete()) {
                        dProcess.doRemove(document.getId());
                        doc.removeSubDocument(document.getId());
                    } else if (document.isEdit()) {
                        Collection<ErrorMessage> subErrors = validateDocument(params, document, applicationId, user);
                        if (!subErrors.isEmpty()) {
                            //return Resource.error(4001, "子表单校验不通过", subErrors);
                            return ExecuteActionResultDto.create(ExecuteActionResultDto.ActionResultType.Error,
                                    Resource.error(4001, "子表单校验不通过", subErrors));
                        }
                        dProcess.doCreateOrUpdate(document, user);
                    }
                }
                //在保存完子表之后重计算一下主表
                doc = doc.getForm().recalculateDocument(doc, params, user);
            }
            FlowRuntimeService stateProcess = new FlowRuntimeServiceImpl(applicationId);
            if (!StringUtil.isBlank(doc.getStateid())) {
                FlowStateRT instance = (FlowStateRT) stateProcess.findFlowStateRT(doc.getStateid());
                if (instance == null) {
                    doc.setStateid("");
                }
            }
            dProcess.doCreateOrUpdate(doc, user);
           //MemoryCacheUtil.putToPrivateSpace(doc.getId(), doc, user);
            //return Resource.ok(doc);

            return ExecuteActionResultDto.create(ExecuteActionResultDto.ActionResultType.OK, Resource.ok(doc,ActionStep.ActionCore.name()));
        } else {
           // return Resource.error(4001, "表单校验不通过", errors);
            return ExecuteActionResultDto.create(ExecuteActionResultDto.ActionResultType.Error, Resource.error(4001,ActionStep.ActionCore.name(), "表单校验不通过", errors));
        }
    }

    private ExecuteActionResultDto executeCore_Custom_Button(String applicationId,Document doc,
                                                             ParamsTable params, IUser user,Activity act) throws Exception {
        JSONObject jsonObj = new JSONObject();
        if (!StringUtil.isBlank(act.getActionScript()) || !StringUtil.isBlank(act.getDispatcherUrl())) {
            IRunner runner = JavaScriptFactory.getInstance(user.getSessionid(), applicationId);
            StringBuffer labelAction = new StringBuffer();
            labelAction.append("Activity(").append(act.getId()).append(")." + act.getName()).append(".actionScript");
            runner.initBSFManager(doc, params, user, new ArrayList<ValidateMessage>());
            runner.run(act,labelAction.toString(), act.getActionScript());

            StringBuffer labelDispatcherUrl = new StringBuffer();
            labelDispatcherUrl.append("Activity(").append(act.getId()).append(")." + act.getName())
                    .append(".dispatcherUrlScript");
            String dispatcherUrl = (String) runner.run(act,labelDispatcherUrl.toString(), act.getDispatcherUrl());

            if (!StringUtil.isBlank(dispatcherUrl)) {
                jsonObj.put("dispatcherUrl", dispatcherUrl);
            }
        }

        return ExecuteActionResultDto.create(ExecuteActionResultDto.ActionResultType.OK, Resource.ok(jsonObj));
    }

    private ExecuteActionResultDto executeCore_runAfter(ParamsTable params,
                                             Document doc,
                                             IUser user,Activity act) throws Exception {

        JSONObject jsonObj = new JSONObject();

        if (!StringUtil.isBlank(act.getAfterActionScript()) || !StringUtil.isBlank(act.getRetractAfterActionScript())) {
            params.setParameter("_flowType", doc.getLastFlowOperation());
            Object message = act.runAfterActionScript(doc, params, user);
            if (message != null) {
                if (message instanceof String && ((String) message).trim().length() > 0) {
                    message = new JsMessage(JsMessage.TYPE_SUCCESS, (String) message);
                }
                if (message instanceof JsMessage) {
                    jsonObj.put("type", "message");
                    jsonObj.put("content", message);
                    jsonObj.put("step", ActionStep.AfterAction);
                    jsonObj.put("next-step", ActionStep.AfterAction);
                }

                return ExecuteActionResultDto.create(ExecuteActionResultDto.ActionResultType.Message, Resource.ok(jsonObj, ActionStep.AfterAction.name()));
            }
        }

        return ExecuteActionResultDto.create(ExecuteActionResultDto.ActionResultType.OK, Resource.ok(jsonObj, ActionStep.AfterAction.name()));
    }

    private Collection<ErrorMessage> validateDocument(ParamsTable params, Document doc, String applicationId,
                                                      IUser webUser) throws Exception {
        Collection<ErrorMessage> errmsgs = new ArrayList<ErrorMessage>();
        doc.setDomainid(webUser.getDomainid());

        DocumentProcess proxy = RunTimeServiceManager.documentProcess(applicationId);
        Collection<ValidateMessage> errors = proxy.doValidate(doc, params, webUser);
        if (errors != null && errors.size() > 0) {
            for (Iterator<ValidateMessage> iter = errors.iterator(); iter.hasNext(); ) {
                ValidateMessage err = (ValidateMessage) iter.next();
                ErrorMessage msg = new ErrorMessage();
                msg.setErrcode(40001);
                msg.setErrmsg(err.getErrmessage());
                msg.setField(err.getFieldname());
                errmsgs.add(msg);
            }
        }
        return errmsgs;
    }

    private Activity getSelectedActivity(String applicationId,String id, Document doc, ParamsTable params) throws Exception {
        ActivityParent actParent = null;
        Activity act = null;
        String docid = params.getParameterAsString("docId");
        String viewid = params.getParameterAsString("viewId");
        String formid = params.getParameterAsString("formId");
        String templateForm = params.getParameterAsString("_templateForm");
        String parentId = params.getParameterAsString("parentId");
        params.setParameter("application", applicationId);
        params.setParameter("docid", docid);
        params.setParameter("viewid", viewid);
        params.setParameter("formid", formid);
        params.setParameter("parentid", parentId);

        if (docid != null && docid.equals(parentId)) {
            parentId = null;
            params.removeParameter("parentid");
        }
        if (id != null && id.trim().length() > 0) {
            if (!StringUtil.isBlank(formid) || !StringUtil.isBlank(templateForm)) {
                FormDesignTimeService formService = DesignTimeServiceManager.formDesignTimeService();
                if (!StringUtil.isBlank(templateForm)) {
                    actParent = (ActivityParent) formService.doView(templateForm);
                    act = actParent.findActivity(id);
                }
                if (act == null) {
                    actParent = (ActivityParent) formService.doView(formid);
                    if (actParent != null) {
                        act = actParent.findActivity(id);
                    }
                }
            } else {
                if (act == null && !StringUtil.isBlank(viewid)) {
                    ViewDesignTimeService viewService = DesignTimeServiceManager.viewDesignTimeService();
                    actParent = (ActivityParent) viewService.doView(viewid);
                    act = actParent.findActivity(id);
                } else {
                    ActivityDesignTimeService activityService = DesignTimeServiceManager.activityDesignTimeService();
                    act = activityService.findById(id);
                }
            }
        }

        if (act == null) {
            ActivityDesignTimeService activityService = DesignTimeServiceManager.activityDesignTimeService();
            act = activityService.findById(id);
        }
        //act.setType(13);

        return act;
    }
}
