package com.kingdee.eas.custom.weaverTodo.server;

import com.alibaba.fastjson.JSONObject;
import com.kingdee.bos.Context;
import com.kingdee.bos.dao.ormapping.ObjectUuidPK;
import com.kingdee.bos.workflow.metas.ProcInstFactory;
import com.kingdee.bos.workflow.metas.ProcInstInfo;
import com.kingdee.bos.workflow.metas.WfState;
import com.kingdee.eas.base.message.webservice.WSMessage;
import com.kingdee.eas.base.message.webservice.WfrAssignMessage;
import com.kingdee.eas.custom.weaverTodo.client.WeaverReceiveTodoClient;
import com.kingdee.eas.custom.weaverTodo.config.WeaverTodoConfig;
import com.kingdee.eas.custom.weaverTodo.enums.WeaverCallOperTypeEnum;
import com.kingdee.eas.custom.weaverTodo.enums.WeaverIsRemarkEnum;
import com.kingdee.eas.custom.weaverTodo.enums.WeaverRequestStatusEnum;
import com.kingdee.eas.custom.weaverTodo.enums.WeaverViewTypeEnum;
import com.kingdee.eas.custom.weaverTodo.util.WeaverPushTrace;
import com.kingdee.eas.custom.weaverTodo.util.WeaverTodoSupport;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

/**
 * 待办：isremark 0/2/4/-1 映射到统一待办接口。
 * 注意：重新指定执行人时平台仍调 addCompletedMessage，但 state=CANCELED，需改推 -1。
 */
public class WeaverTodoMessageServer {
    private static final Logger logger = Logger.getLogger(WeaverTodoMessageServer.class);

    private final WeaverTodoConfig config;
    private final WeaverTodoSupport support;
    private final WeaverReceiveTodoClient client;

    public WeaverTodoMessageServer(WeaverTodoConfig config,
                                   WeaverTodoSupport support,
                                   WeaverReceiveTodoClient client) {
        this.config = config;
        this.support = support;
        this.client = client;
    }

    public boolean addMessage(WSMessage message, String traceId) {
        String tid = nvlTrace(traceId);
        Context ctx = message.getContext();
        String msgId = message.getMsgID();
        String assignId = "";
        String procInstId = message.getProcInstId();
        String billId = message.getBillId();
        logger.info("[WEAVER-TODO][" + tid + "] 业务=新增待办 BEGIN msgId=" + msgId
                + " procInstId=" + procInstId + " billId=" + billId
                + " targetIsremark=0");
        try {
            // 先拿 AssignmentId 再 sleep：sleep 期间若撤回，AssignRead 可能已没，后查会空（对齐 YH）
            assignId = waitAssignIdByMsgId(ctx, msgId, tid);
            if (StringUtils.isBlank(assignId)) {
                logger.error("[WEAVER-TODO][" + tid + "] 业务=新增待办 放弃：AssignmentId 为空 msgId=" + msgId);
                support.addCallLog(ctx, msgId, "", billId, WeaverCallOperTypeEnum.TODO_ADD,
                        support.buildReceiveTodoUrl(),
                        "", "assignId empty", 0, "traceId=" + tid + " assignId empty");
                return false;
            }
            if (StringUtils.isBlank(procInstId)) {
                logger.error("[WEAVER-TODO][" + tid + "] 业务=新增待办 放弃：procInstId 为空 msgId=" + msgId
                        + " assignId=" + assignId);
                support.addCallLog(ctx, msgId, assignId, billId, WeaverCallOperTypeEnum.TODO_ADD,
                        support.buildReceiveTodoUrl(),
                        "", "procInstId empty", 0, "traceId=" + tid + " procInstId empty");
                return false;
            }
            billId = support.resolveBillId(ctx, billId, assignId);
            String workflowname = support.resolveWorkflowName(ctx, msgId, billId, assignId);

            String receiverKey = message.getRecieverID();
            if (StringUtils.isBlank(receiverKey)) {
                receiverKey = message.getRecieveNumber();
            }
            logger.info("[WEAVER-TODO][" + tid + "] 业务=新增待办 解析人"
                    + " msgId=" + msgId + " assignId=" + assignId
                    + " receiverKey=" + receiverKey
                    + " receivers=" + message.getReceivers()
                    + " workflowname=" + workflowname);

            String receiver = support.resolveReceiverWorkcode(ctx, receiverKey);
            if (StringUtils.isBlank(receiver)) {
                logger.error("[WEAVER-TODO][" + tid + "] 业务=新增待办 放弃：接收人工号为空"
                        + " msgId=" + msgId + " key=" + receiverKey);
                return false;
            }

            String creator = support.resolveReceiverWorkcode(ctx, message.getSenderNumber());
            String title = StringUtils.defaultIfBlank(message.getTitle(), workflowname);
            String nodename = StringUtils.defaultIfBlank(message.getActName(), "审批节点");
            String pcurl = support.buildRelativePcUrl(ctx, billId, assignId);
            String appurl = support.buildRelativeAppUrl(ctx, billId, assignId);

            // 关键落本地关联，避免 sleep 期间撤回后本地也查不到
            support.saveAssignRecord(ctx, msgId, assignId, billId, workflowname, procInstId, receiver);

            // 再等一会，让「待办转已办」等并发先处理完再推，顺序与 YH 一致
            support.delayForWf();

            JSONObject payload = support.buildPayload(
                    procInstId, title, workflowname, nodename, receiver, creator,
                    pcurl, appurl, WeaverIsRemarkEnum.TODO, WeaverViewTypeEnum.UNREAD,
                    WeaverRequestStatusEnum.APPROVING);
            logger.info("[WEAVER-TODO][" + tid + "] 业务=新增待办 即将推送 "
                    + WeaverPushTrace.summarize(payload)
                    + " assignId=" + assignId + " creator=" + creator
                    + " pcurl=" + pcurl);

            boolean ok = client.push(payload, tid);
            support.addCallLog(ctx, msgId, assignId, billId, WeaverCallOperTypeEnum.TODO_ADD,
                    support.buildReceiveTodoUrl(),
                    payload.toJSONString(), String.valueOf(ok), ok ? 1 : 0,
                    "traceId=" + tid + " isremark=0 " + (ok ? "成功" : "失败"));
            logger.info("[WEAVER-TODO][" + tid + "] 业务=新增待办 END ok=" + ok
                    + " " + WeaverPushTrace.summarize(payload));
            return ok;
        } catch (Exception e) {
            logger.error("[WEAVER-TODO][" + tid + "] 业务=新增待办 异常 msgId=" + msgId
                    + " assignId=" + assignId, e);
            support.addCallLog(ctx, msgId, assignId, billId, WeaverCallOperTypeEnum.TODO_ADD,
                    support.buildReceiveTodoUrl(),
                    "", e.getMessage(), 0, "traceId=" + tid + " " + e.getMessage());
            return false;
        }
    }

    public boolean addCompletedMessage(WfrAssignMessage wfrAssignMessage, String traceId) {
        String tid = nvlTrace(traceId);
        Context ctx = wfrAssignMessage.getCtx();
        String assignId = wfrAssignMessage.getAssignId();
        String procInstId = wfrAssignMessage.getProcInstId();
        String msgId = "";
        String assignState = wfrAssignMessage.getState();
        String assignStateName = wfrAssignMessage.getStateName();
        boolean withdraw = isWithdrawnAssign(assignState, assignStateName);
        logger.info("[WEAVER-TODO][" + tid + "] 业务=已办/办结 BEGIN assignId=" + assignId
                + " procInstId=" + procInstId
                + " assignState=" + assignState
                + " assignStateName=" + assignStateName
                + " withdraw=" + withdraw
                + " targetIsremark=" + (withdraw ? "-1(撤销/重新指定)" : "2(已办)/条件满足再推4"));
        try {
            support.delayForWf();
            msgId = support.getMsgIdByAssignId(ctx, assignId);
            if (StringUtils.isBlank(procInstId)) {
                procInstId = support.getProcInstIdByAssignId(ctx, assignId);
            }
            logger.info("[WEAVER-TODO][" + tid + "] 业务=已办/办结 关联"
                    + " msgId=" + msgId + " assignId=" + assignId
                    + " procInstId=" + procInstId);
            if (StringUtils.isBlank(procInstId)) {
                logger.error("[WEAVER-TODO][" + tid + "] 业务=已办/办结 放弃：procInstId 为空 assignId="
                        + assignId);
                return false;
            }

            String receiver = support.getReceiverByAssignId(ctx, assignId);
            if (StringUtils.isBlank(receiver)) {
                String uid = wfrAssignMessage.getHandlerNumber();
                if (StringUtils.isBlank(uid)) {
                    uid = wfrAssignMessage.getHandlerId();
                }
                logger.info("[WEAVER-TODO][" + tid + "] 业务=已办/办结 从表无receiver，用handler key="
                        + uid);
                receiver = support.resolveReceiverWorkcode(ctx, uid);
            }
            if (StringUtils.isBlank(receiver)) {
                logger.error("[WEAVER-TODO][" + tid + "] 业务=已办/办结 放弃：接收人为空 assignId="
                        + assignId);
                return false;
            }

            String billId = wfrAssignMessage.getBillID();
            String workflowname = support.resolveWorkflowName(ctx, msgId, billId, assignId);
            String title = StringUtils.defaultIfBlank(wfrAssignMessage.getAssignSubject(), workflowname);
            String nodename = StringUtils.defaultIfBlank(wfrAssignMessage.getActName(), "审批节点");
            String pcurl = support.buildRelativePcUrl(ctx, billId, assignId);

            // 重新指定执行人：原任务 CANCELED，平台仍走本方法；必须推 -1 撤回，不能推 2 已办
            String storedName = support.getWorkflowNameByAssignId(ctx, assignId);
            if (StringUtils.isNotBlank(storedName)) {
                workflowname = storedName;
            }

            if (withdraw) {
                JSONObject del = support.buildPayload(
                        procInstId, title, workflowname, nodename, receiver, "",
                        pcurl, pcurl, WeaverIsRemarkEnum.DELETE, WeaverViewTypeEnum.READ, null);
                logger.info("[WEAVER-TODO][" + tid + "] 业务=任务撤销/重新指定 改推删除 "
                        + WeaverPushTrace.summarize(del)
                        + " assignId=" + assignId
                        + " ※原审批人应离开待办，不应进已办；新人靠 addMessage 推 isremark=0");
                boolean ok = client.push(del, tid);
                support.addCallLog(ctx, msgId, assignId, billId, WeaverCallOperTypeEnum.TODO_DELETE,
                        support.buildReceiveTodoUrl(),
                        del.toJSONString(), String.valueOf(ok), ok ? 1 : 0,
                        "traceId=" + tid + " isremark=-1 withdraw state=" + assignState
                                + " " + (ok ? "成功" : "失败"));
                logger.info("[WEAVER-TODO][" + tid + "] 业务=任务撤销 END ok=" + ok
                        + " " + WeaverPushTrace.summarize(del));
                return ok;
            }

            JSONObject done = support.buildPayload(
                    procInstId, title, workflowname, nodename, receiver, "",
                    pcurl, pcurl, WeaverIsRemarkEnum.DONE, WeaverViewTypeEnum.READ,
                    WeaverRequestStatusEnum.APPROVING);
            logger.info("[WEAVER-TODO][" + tid + "] 业务=已办 即将推送 "
                    + WeaverPushTrace.summarize(done)
                    + " assignId=" + assignId
                    + " ※同一 flowid+receiver 会把此前待办(0)覆盖成已办(2)");

            boolean taskOk = client.push(done, tid);
            support.addCallLog(ctx, msgId, assignId, billId, WeaverCallOperTypeEnum.TODO_DONE,
                    support.buildReceiveTodoUrl(),
                    done.toJSONString(), String.valueOf(taskOk), taskOk ? 1 : 0,
                    "traceId=" + tid + " isremark=2 " + (taskOk ? "成功" : "失败"));
            logger.info("[WEAVER-TODO][" + tid + "] 业务=已办 END ok=" + taskOk
                    + " " + WeaverPushTrace.summarize(done));

            ProcInstInfo pInfo = ProcInstFactory.getLocalInstance(ctx).getValue(new ObjectUuidPK(procInstId));
            WfState wfState = pInfo.getState();
            String stateText = wfState == null ? "null" : String.valueOf(wfState);
            String stateAlias = wfState == null ? "null" : wfState.getAlias();
            boolean terminal = isTerminalFlow(wfState);
            logger.info("[WEAVER-TODO][" + tid + "] 业务=办结判断 terminal=" + terminal
                    + " stateAlias=" + stateAlias + " state=" + stateText);

            if (terminal) {
                WeaverRequestStatusEnum endStatus = mapRequestStatus(wfState);
                JSONObject end = support.buildPayload(
                        procInstId, title, workflowname, nodename, receiver, "",
                        pcurl, pcurl, WeaverIsRemarkEnum.FLOW_END, WeaverViewTypeEnum.READ, endStatus);
                logger.info("[WEAVER-TODO][" + tid + "] 业务=办结 即将推送 "
                        + WeaverPushTrace.summarize(end));
                boolean endOk = client.push(end, tid);
                support.addCallLog(ctx, msgId, assignId, billId, WeaverCallOperTypeEnum.FLOW_END,
                        support.buildReceiveTodoUrl(),
                        end.toJSONString(), String.valueOf(endOk), endOk ? 1 : 0,
                        "traceId=" + tid + " isremark=4 " + (endOk ? "成功" : "失败"));
                logger.info("[WEAVER-TODO][" + tid + "] 业务=办结 END ok=" + endOk
                        + " " + WeaverPushTrace.summarize(end));
                return taskOk && endOk;
            }
            return taskOk;
        } catch (Exception e) {
            logger.error("[WEAVER-TODO][" + tid + "] 业务=已办/办结 异常 assignId=" + assignId, e);
            support.addCallLog(ctx, msgId, assignId, "", WeaverCallOperTypeEnum.TODO_DONE,
                    support.buildReceiveTodoUrl(),
                    "", e.getMessage(), 0, "traceId=" + tid + " " + e.getMessage());
            return false;
        }
    }

    public boolean removeMessage(String assignId, Context ctx, String traceId) {
        String tid = nvlTrace(traceId);
        String msgId = "";
        String procInstId = "";
        logger.info("[WEAVER-TODO][" + tid + "] 业务=删除待办 BEGIN assignId=" + assignId
                + " targetIsremark=-1");
        try {
            support.delayForWf();
            msgId = support.getMsgIdByAssignId(ctx, assignId);
            procInstId = support.getProcInstIdByAssignId(ctx, assignId);
            String receiver = support.getReceiverByAssignId(ctx, assignId);
            logger.info("[WEAVER-TODO][" + tid + "] 业务=删除待办 关联 msgId=" + msgId
                    + " procInstId=" + procInstId + " receiver=" + receiver);
            if (StringUtils.isBlank(procInstId) || StringUtils.isBlank(receiver)) {
                logger.error("[WEAVER-TODO][" + tid + "] 业务=删除待办 放弃：缺 flowid/receiver assignId="
                        + assignId);
                return false;
            }
            String billId = support.getBillIdByAssignId(ctx, assignId);
            String workflowname = support.getWorkflowNameByAssignId(ctx, assignId);
            if (StringUtils.isBlank(workflowname)) {
                workflowname = support.resolveWorkflowName(ctx, msgId, billId, assignId);
            }
            logger.info("[WEAVER-TODO][" + tid + "] 业务=删除待办 键信息 billId=" + billId
                    + " workflowname=" + workflowname
                    + " ※须与当初待办 workflowname 一致才能删中");
            JSONObject payload = support.buildPayload(
                    procInstId, workflowname, workflowname, "审批节点", receiver, "",
                    null, null, WeaverIsRemarkEnum.DELETE, WeaverViewTypeEnum.READ, null);
            logger.info("[WEAVER-TODO][" + tid + "] 业务=删除待办 即将推送 "
                    + WeaverPushTrace.summarize(payload));
            boolean ok = client.push(payload, tid);
            support.addCallLog(ctx, msgId, assignId, "", WeaverCallOperTypeEnum.TODO_DELETE,
                    support.buildReceiveTodoUrl(),
                    payload.toJSONString(), String.valueOf(ok), ok ? 1 : 0,
                    "traceId=" + tid + " isremark=-1 " + (ok ? "成功" : "失败"));
            logger.info("[WEAVER-TODO][" + tid + "] 业务=删除待办 END ok=" + ok);
            return ok;
        } catch (Exception e) {
            logger.error("[WEAVER-TODO][" + tid + "] 业务=删除待办 异常 assignId=" + assignId, e);
            support.addCallLog(ctx, msgId, assignId, "", WeaverCallOperTypeEnum.TODO_DELETE,
                    support.buildReceiveTodoUrl(),
                    "", e.getMessage(), 0, "traceId=" + tid + " " + e.getMessage());
            return false;
        }
    }

    /**
     * 重新指定/撤销任务时 assignment 状态为 CANCELED，仍会进 addCompletedMessage。
     */
    private boolean isWithdrawnAssign(String state, String stateName) {
        String s = StringUtils.defaultString(state);
        String n = StringUtils.defaultString(stateName);
        return StringUtils.containsIgnoreCase(s, "CANCEL")
                || StringUtils.containsIgnoreCase(s, "ABORT")
                || StringUtils.containsIgnoreCase(n, "取消")
                || StringUtils.containsIgnoreCase(n, "撤销")
                || StringUtils.containsIgnoreCase(n, "作废");
    }

    private boolean isTerminalFlow(WfState wfState) {
        if (wfState == null) {
            return false;
        }
        String alias = wfState.getAlias();
        return StringUtils.containsIgnoreCase(alias, "完成")
                || StringUtils.containsIgnoreCase(alias, "终止")
                || StringUtils.containsIgnoreCase(alias, "结束")
                || StringUtils.containsIgnoreCase(String.valueOf(wfState), "COMPLETED")
                || StringUtils.containsIgnoreCase(String.valueOf(wfState), "TERMINATED")
                || StringUtils.containsIgnoreCase(String.valueOf(wfState), "ABORTED");
    }

    private WeaverRequestStatusEnum mapRequestStatus(WfState wfState) {
        if (wfState == null) {
            return WeaverRequestStatusEnum.ARCHIVED;
        }
        String s = String.valueOf(wfState);
        if (StringUtils.containsIgnoreCase(s, "ABORT") || StringUtils.containsIgnoreCase(s, "TERMINAT")) {
            return WeaverRequestStatusEnum.FORCE_ARCHIVED;
        }
        return WeaverRequestStatusEnum.ARCHIVED;
    }


    /**
     * 短轮询拿 AssignmentId：平台刚写 AssignRead 时可能尚未可见；
     * 绝不先长 sleep 再查，否则 sleep 中撤回后就查空了。
     */
    private String waitAssignIdByMsgId(Context ctx, String msgId, String tid) throws Exception {
        long budgetMs = Math.max(500L, config.getWfDelayMs());
        long stepMs = 500L;
        long deadline = System.currentTimeMillis() + budgetMs;
        String assignId = "";
        int attempt = 0;
        while (true) {
            attempt++;
            assignId = support.getAssignIdByMsgId(ctx, msgId);
            if (StringUtils.isNotBlank(assignId)) {
                if (attempt > 1) {
                    logger.info("[WEAVER-TODO][" + tid + "] 业务=新增待办 AssignmentId 第"
                            + attempt + "次查到 assignId=" + assignId);
                }
                return assignId;
            }
            long left = deadline - System.currentTimeMillis();
            if (left <= 0) {
                break;
            }
            try {
                Thread.sleep(Math.min(stepMs, left));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
        return "";
    }

    private static String nvlTrace(String traceId) {
        return StringUtils.isBlank(traceId) ? "-" : traceId;
    }
}
