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

import com.alibaba.fastjson.JSONObject;
import com.kingdee.bos.BOSException;
import com.kingdee.bos.Context;
import com.kingdee.bos.dao.ormapping.ObjectUuidPK;
import com.kingdee.bos.metadata.IMetaDataLoader;
import com.kingdee.bos.metadata.MetaDataLoaderFactory;
import com.kingdee.bos.metadata.entity.EntityObjectInfo;
import com.kingdee.bos.util.BOSUuid;
import com.kingdee.eas.base.message.AssignReadFactory;
import com.kingdee.eas.base.message.AssignReadInfo;
import com.kingdee.eas.base.message.IAssignRead;
import com.kingdee.eas.base.message.URLInfo;
import com.kingdee.eas.base.message.util.ProcessCenterUtil;
import com.kingdee.eas.base.permission.IUser;
import com.kingdee.eas.base.permission.UserFactory;
import com.kingdee.eas.base.permission.UserInfo;
import com.kingdee.eas.common.EASBizException;
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.util.app.DbUtil;
import com.kingdee.jdbc.rowset.IRowSet;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 统一待办字段拼装、相对链接、人员工号、本地日志表。
 */
public class WeaverTodoSupport {
    private static final Logger logger = Logger.getLogger(WeaverTodoSupport.class);
    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private final WeaverTodoConfig config;

    public WeaverTodoSupport(WeaverTodoConfig config) {
        this.config = config;
    }

    public void delayForWf() {
        try {
            Thread.sleep(config.getWfDelayMs());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public String sanitize(String text) {
        if (text == null) {
            return "";
        }
        String s = text.replace("'", "")
                .replace("&nbsp;", " ")
                .replace("&lt;", "")
                .replace("&gt;", "")
                .replace("&amp;", "")
                .replace("\"", "");
        if (s.length() > 2000) {
            s = s.substring(0, 2000);
        }
        return s.trim();
    }

    public String nowDatetime() {
        return SDF.format(new Date());
    }

    public String workflowCodeOf(String workflowname) {
        String name = sanitize(workflowname);
        if (StringUtils.isBlank(name)) {
            name = "SHR流程";
        }
        return String.valueOf(name.hashCode());
    }

    public String getAssignIdByMsgId(Context ctx, String msgId) throws BOSException, EASBizException {
        if (StringUtils.isBlank(msgId)) {
            return "";
        }
        IAssignRead assignReadIns = AssignReadFactory.getLocalInstance(ctx);
        AssignReadInfo assignReadInfo = assignReadIns.getAssignReadInfo(new ObjectUuidPK(msgId));
        if (assignReadInfo == null || assignReadInfo.getAssignID() == null) {
            return "";
        }
        return assignReadInfo.getAssignID().toString();
    }

    public String resolveBillId(Context ctx, String billId, String assignId) throws Exception {
        if (StringUtils.isNotBlank(billId)) {
            return billId;
        }
        if (StringUtils.isBlank(assignId)) {
            return "";
        }
        URLInfo info = ProcessCenterUtil.getUrlInfos(assignId, ctx);
        return info == null ? "" : StringUtils.defaultString(info.getBillID());
    }

    public String resolveWorkflowName(Context ctx, String msgId, String billId, String assignId) {
        String flowTypeName = "SHR流程";
        try {
            String resolvedBill = resolveBillId(ctx, billId, assignId);
            if (StringUtils.isBlank(resolvedBill) && StringUtils.isNotBlank(msgId)) {
                assignId = getAssignIdByMsgId(ctx, msgId);
                resolvedBill = resolveBillId(ctx, billId, assignId);
            }
            if (StringUtils.isBlank(resolvedBill)) {
                return flowTypeName;
            }
            IMetaDataLoader loader = MetaDataLoaderFactory.getRemoteMetaDataLoader();
            EntityObjectInfo vo = loader.getEntity(BOSUuid.read(resolvedBill).getType());
            if (vo != null && StringUtils.isNotBlank(vo.getDisplayName())) {
                flowTypeName = vo.getDisplayName();
            }
        } catch (Exception e) {
            logger.warn("解析流程类型名失败，使用默认", e);
        }
        return sanitize(flowTypeName);
    }

    /**
     * 报业返回人员工号 pp.FNumber（与 SSO PERSON_NUMBER 一致）
     */
    public String resolveReceiverWorkcode(Context ctx, String userIdOrNumber) {
        if (StringUtils.isBlank(userIdOrNumber)) {
            return "";
        }
        String key = userIdOrNumber.trim();
        try {
            String sql = "select  uu.FNumber userNumber from  T_PM_User uu   "
                    + "where uu.FForbidden = 0 and (uu.FNumber = ? or uu.FID = ?) "
                    + "order by uu.FIsDelete";
            IRowSet rs = DbUtil.executeQuery(ctx, sql, new Object[]{key, key});
            if (rs.next()) {
                String userNumber = rs.getString("userNumber");
                if (StringUtils.isNotBlank(userNumber)) {
                    return userNumber.trim();
                }
            }
        } catch (Exception e) {
            logger.warn("SQL 解析工号失败 key=" + key + ": " + e.getMessage());
        }
        return "";
    }

    
    /**
     * 按用户 id 取 UserInfo（BMC 发送人等）。
     */
    public UserInfo getUserById(Context ctx, String userId) {
        if (StringUtils.isBlank(userId)) {
            return null;
        }
        try {
            IUser iUser = UserFactory.getLocalInstance(ctx);
            return iUser.getUserInfo(new ObjectUuidPK(userId.trim()));
        } catch (Exception e) {
            logger.warn("按 id 取用户失败 userId=" + userId + ": " + e.getMessage());
            return null;
        }
    }

/**
     * 泛微要求相对路径。落地走 SSO：entryPath?appId&redirect=审批页绝对地址(编码)。
     * entryPath 来自 weaver.sso.entryPath，默认 /qy/auth/weaver/app.do。
     */
    public String buildRelativePcUrl(Context ctx, String billId, String assignId) throws Exception {
        String resolvedBill = resolveBillId(ctx, billId, assignId);
        String approveAbs;
        if (StringUtils.isNotBlank(assignId)) {
            approveAbs = config.getShrServerName() + config.getApprovePcPath()
                    + "?AssignmentId=" + URLEncoder.encode(assignId, StandardCharsets.UTF_8.name())
                    + "&billID=" + URLEncoder.encode(StringUtils.defaultString(resolvedBill), StandardCharsets.UTF_8.name());
        } else {
            approveAbs = config.getShrServerName() + "/shr/dynamic.do?uipk=shr.workflow.view&inFrame=true";
        }
        String entryPath = StringUtils.defaultIfBlank(config.getSsoEntryPath(), "/qy/auth/weaver/app.do");
        String relative = entryPath + "?appId=" + URLEncoder.encode(config.getSsoAppId(), StandardCharsets.UTF_8.name())
                + "&redirect=" + URLEncoder.encode(approveAbs, StandardCharsets.UTF_8.name());
        if (relative.startsWith("http://") || relative.startsWith("https://")) {
            throw new IllegalStateException("pcurl 不允许绝对地址");
        }
        return relative;
    }

    public String buildRelativeAppUrl(Context ctx, String billId, String assignId) throws Exception {
        // 移动端暂与 PC 同路径，由泛微外链前缀区分端；后续可接 MBOS
        return buildRelativePcUrl(ctx, billId, assignId);
    }

    public JSONObject buildPayload(String flowid,
                                   String requestname,
                                   String workflowname,
                                   String nodename,
                                   String receiver,
                                   String creator,
                                   String pcurl,
                                   String appurl,
                                   WeaverIsRemarkEnum isRemark,
                                   WeaverViewTypeEnum viewType,
                                   WeaverRequestStatusEnum requestStatus) {
        JSONObject body = new JSONObject();
        body.put("syscode", config.getSyscode());
        body.put("flowid", StringUtils.defaultString(flowid));
        body.put("requestname", sanitize(requestname));
        String typeName = sanitize(workflowname);
        if (StringUtils.isBlank(typeName)) {
            typeName = "SHR流程";
        }
        body.put("workflowname", typeName);
        body.put("workflowcode", workflowCodeOf(typeName));
        body.put("nodename", sanitize(StringUtils.defaultIfBlank(nodename, "审批节点")));
        body.put("createdatetime", nowDatetime());
        body.put("receiver", sanitize(receiver));
        body.put("receivedatetime", nowDatetime());
        body.put("isremark", isRemark.getCode());
        body.put("viewtype", viewType.getCode());
        body.put("receivets", String.valueOf(System.currentTimeMillis()));
        if (StringUtils.isNotBlank(creator)) {
            body.put("creator", sanitize(creator));
        }
        if (StringUtils.isNotBlank(pcurl)) {
            body.put("pcurl", pcurl);
        }
        if (StringUtils.isNotBlank(appurl)) {
            body.put("appurl", appurl);
        }
        if (requestStatus != null) {
            body.put("requestStatus", requestStatus.getCode());
        }
        return body;
    }

    public void saveAssignRecord(Context ctx, String msgId, String assignId, String billId,
                                 String workflowname, String procInstId, String receiver) {
        // 优先带 receiver 列；老表没有该列则回退
        try {
            String sql = "insert into assignReadRecord(MsgId,assignID,billId,workflowname,procInstId,receiver) values(?,?,?,?,?,?)";
            Object[] params = new Object[]{
                    nvl(msgId), nvl(assignId), nvl(billId), nvl(workflowname), nvl(procInstId), nvl(receiver)
            };
            DbUtil.execute(ctx, sql, params);
            return;
        } catch (Exception e) {
            logger.warn("带 receiver 写入 assignReadRecord 失败，尝试无 receiver 列: " + e.getMessage());
        }
        try {
            String sql = "insert into assignReadRecord(MsgId,assignID,billId,workflowname,procInstId) values(?,?,?,?,?)";
            Object[] params = new Object[]{
                    nvl(msgId), nvl(assignId), nvl(billId), nvl(workflowname), nvl(procInstId)
            };
            DbUtil.execute(ctx, sql, params);
        } catch (Exception e) {
            logger.warn("写入 assignReadRecord 失败(表可能已存在同键或未建表): " + e.getMessage());
        }
    }

    /** 统一待办接口 URL（不含 access_token，便于落库）。 */
    public String buildReceiveTodoUrl() {
        return StringUtils.defaultString(config.getOpenapiHost())
                + StringUtils.defaultString(config.getReceivePath());
    }

    /** 消息中心 sendMsg URL（不含 access_token）。 */
    public String buildSendMsgUrl() {
        return StringUtils.defaultString(config.getOpenapiHost())
                + StringUtils.defaultString(config.getSendMsgPath());
    }

    public void addCallLog(Context ctx, String msgId, String assignId, String billId,
                           WeaverCallOperTypeEnum operType, String requestUrl,
                           String requestParams, String responseResult,
                           int statusCode, String message) {
        try {
            logger.info("[WEAVER-TODO] CallLog write OperType=" + (operType == null ? "" : operType.getLabel())
                    + " msgId=" + msgId + " assignId=" + assignId + " billId=" + billId
                    + " requestUrl=" + requestUrl
                    + " statusCode=" + statusCode + " message=" + message);

            String sql = "insert into OAMsgCallLog(MsgId,assignID,billId,OperType,RequestUrl,RequestParams,ResponseResult,StatusCode,Message,CreateTime) "
                    + "values(?,?,?,?,?,?,?,?,?,?)";
            String operLabel = operType == null ? "" : operType.getLabel();
            Object[] params = new Object[]{
                    msgId, assignId, billId, operLabel,
                    StringUtils.defaultString(requestUrl),
                    requestParams, responseResult,
                    statusCode, message, new Timestamp(System.currentTimeMillis())
            };
            DbUtil.execute(ctx, sql, params);
        } catch (Exception e) {
            logger.warn("写入 OAMsgCallLog 失败: " + e.getMessage());
        }
    }

    /**
     * 截断字符串
     * @param s
     * @param maxLen
     * @return
     */
    private static String cutStr(String s, int maxLen) {
        if (s == null) {
            return "";
        }
        if (s.length() <= maxLen) {
            return s;
        }
        return s.substring(0, maxLen);
    }

    public String getMsgIdByAssignId(Context ctx, String assignId) {
        try {
            IRowSet rs = DbUtil.executeQuery(ctx, "select MsgId from assignReadRecord where assignID = ?",
                    new Object[]{assignId});
            if (rs.next()) {
                return rs.getString("MsgId");
            }
        } catch (Exception e) {
            logger.warn("按 assignId 查 msgId 失败", e);
        }
        return "";
    }

    public String getProcInstIdByAssignId(Context ctx, String assignId) {
        try {
            IRowSet rs = DbUtil.executeQuery(ctx, "select procInstId from assignReadRecord where assignID = ?",
                    new Object[]{assignId});
            if (rs.next()) {
                return rs.getString("procInstId");
            }
        } catch (Exception e) {
            logger.warn("按 assignId 查 procInstId 失败", e);
        }
        return "";
    }

    public String getBillIdByAssignId(Context ctx, String assignId) {
        try {
            IRowSet rs = DbUtil.executeQuery(ctx, "select billId from assignReadRecord where assignID = ?",
                    new Object[]{assignId});
            if (rs.next()) {
                return StringUtils.defaultString(rs.getString("billId"));
            }
        } catch (Exception e) {
            logger.warn("get billId by assignId failed", e);
        }
        return "";
    }

    /**
     * 删除/撤销必须用当初推待办的 workflowname，否则泛微 upsert 键对不上。
     */
    public String getWorkflowNameByAssignId(Context ctx, String assignId) {
        try {
            IRowSet rs = DbUtil.executeQuery(ctx, "select workflowname from assignReadRecord where assignID = ?",
                    new Object[]{assignId});
            if (rs.next()) {
                String name = rs.getString("workflowname");
                if (StringUtils.isNotBlank(name)) {
                    return sanitize(name);
                }
            }
        } catch (Exception e) {
            logger.warn("get workflowname by assignId failed: " + e.getMessage());
        }
        try {
            IRowSet rs = DbUtil.executeQuery(ctx,
                    "select RequestParams from OAMsgCallLog where assignID = ? and OperType = ? order by CreateTime desc",
                    new Object[]{assignId, WeaverCallOperTypeEnum.TODO_ADD.getLabel()});
            if (rs.next()) {
                String raw = rs.getString("RequestParams");
                if (StringUtils.isNotBlank(raw)) {
                    JSONObject json = JSONObject.parseObject(raw);
                    String name = json.getString("workflowname");
                    if (StringUtils.isNotBlank(name)) {
                        return sanitize(name);
                    }
                }
            }
        } catch (Exception e) {
            logger.warn("parse workflowname from CallLog failed", e);
        }
        return "";
    }

    public String getReceiverByAssignId(Context ctx, String assignId) {
        try {
            IRowSet rs = DbUtil.executeQuery(ctx, "select receiver from assignReadRecord where assignID = ?",
                    new Object[]{assignId});
            if (rs.next()) {
                String r = rs.getString("receiver");
                if (StringUtils.isNotBlank(r)) {
                    return r;
                }
            }
        } catch (Exception e) {
            logger.warn("按 assignId 查 receiver 列失败，回退 CallLog: " + e.getMessage());
        }
        try {
            IRowSet rs = DbUtil.executeQuery(ctx,
                    "select RequestParams from OAMsgCallLog where assignID = ? and OperType = ? order by CreateTime desc",
                    new Object[]{assignId, WeaverCallOperTypeEnum.TODO_ADD.getLabel()});
            if (rs.next()) {
                String raw = rs.getString("RequestParams");
                if (StringUtils.isNotBlank(raw)) {
                    JSONObject json = JSONObject.parseObject(raw);
                    String r = json.getString("receiver");
                    if (StringUtils.isNotBlank(r)) {
                        return r;
                    }
                }
            }
        } catch (Exception e) {
            logger.warn("从 OAMsgCallLog 解析 receiver 失败", e);
        }
        return "";
    }

    private static String nvl(String s) {
        return s == null ? "" : s;
    }
}
