package org.springblade.thirdparty.mk.service.impl;

import cn.hutool.json.JSONUtil;
import feign.Response;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.tool.utils.CollectionUtil;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.UrlUtil;
import org.springblade.thirdparty.mk.config.MKProperties;
import org.springblade.thirdparty.mk.constant.MKConstant;
import org.springblade.thirdparty.mk.exception.MKException;
import org.springblade.thirdparty.mk.feign.IMKClient;
import org.springblade.thirdparty.mk.pojo.dto.*;
import org.springblade.thirdparty.mk.pojo.dto.approval.MKApprovalDTO;
import org.springblade.thirdparty.mk.pojo.dto.approval.MKProcessDTO;
import org.springblade.thirdparty.mk.pojo.vo.*;
import org.springblade.thirdparty.mk.service.IMKService;
import org.springblade.thirdparty.mk.util.AssertUtils;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author bfhuange
 * @date 2024/9/11
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class MKServiceImpl implements IMKService {

    /**
     *  mk审批页面链接
     */
    private static final String APPROVAL_URL_FORMAT = "%s/web//#/current/sys-lbpm/lbpmIntegrate/formInFlow/view/%s?processInstanceId=%s";
    /**
     *  mk sso回调地址
     */
    private static final String SSO_CALLBACK_URL_FORMAT = "%s/data/sys-auth/ssoCallBack?mkCode=%s&redirectUrl=%s";

    private final MKProperties properties;
    private final IMKClient client;
    private final BladeRedis bladeRedis;

    @Override
    public String processSubmit(MKProcessCreateDTO param) {
        if (param == null) {
            log.warn("调用mk接口 创建并提交流程参数为空");
            return null;
        }
        return commonCall("创建并提交流程", token -> {
                    // 设置标题
                    String actualSubject = getActualSubject(param.getSubject());
                    param.setSubject(actualSubject);
                    log.info("调用mk提交流程开始");
                    long start = System.currentTimeMillis();
                    MKResultVO<String> result = client.processSubmit(param, token);
                    long end = System.currentTimeMillis();
                    log.info("调用mk提交流程结束 耗时：{}", end - start);
                    return result;
                }, result -> Optional.ofNullable(result)
                        .map(MKResultVO::getData)
                        .orElse(null)
        );
    }

    @Override
    public boolean processExecute(MKProcessExecuteDTO param) {
        if (param == null) {
            log.warn("调用mk接口 执行流程参数为空");
            return false;
        }
        return commonCall("执行流程", token -> client.processExecute(param, token));
    }

    @Override
    public boolean processRevoke(String bizId, String loginName) {
        if (StringUtil.isBlank(bizId) || StringUtil.isBlank(loginName)) {
            log.warn("调用mk接口 撤回流程参数为空");
            return false;
        }
        return commonCall("撤回流程", token -> {
            MKProcessExecuteDTO param = new MKProcessExecuteDTO();
            param.setFormInstanceId(bizId);
            param.setLoginName(loginName);
            param.setOperationType(MKConstant.OPERATE_TYPE_REVOKE);
            return client.processExecute(param, token);
        });
    }

    @Override
    public boolean processDelete(String processInstanceId, String loginName) {
        if (StringUtil.isBlank(processInstanceId) || StringUtil.isBlank(loginName)) {
            log.warn("调用mk接口 删除流程参数为空");
            return false;
        }
        return commonCall("删除流程", token -> {
            MKProcessDeleteDTO param = new MKProcessDeleteDTO();
            param.setProcessInstanceId(processInstanceId);
            param.setLoginName(loginName);
            return client.processDelete(param, token);
        });
    }

    @Override
    public boolean processAbandon(String bizId, String loginName) {
        if (StringUtil.isBlank(bizId) || StringUtil.isBlank(loginName)) {
            log.warn("调用mk接口 废弃流程参数为空");
            return false;
        }
        return commonCall("废弃流程", token -> {
            MKProcessExecuteDTO param = new MKProcessExecuteDTO();
            param.setFormInstanceId(bizId);
            param.setLoginName(loginName);
            param.setOperationType(MKConstant.OPERATE_TYPE_ABANDON);
            return client.processExecute(param, token);
        });
    }

    /**
     *  公共调用方法
     * @param type
     * @param function
     * @return
     */
    private <T> boolean commonCall(String type, Function<String, MKResultVO<T>> function) {
        return commonCall(type, function, Objects::nonNull);
    }

    /**
     *  公共调用方法
     * @param type
     * @param function
     * @return
     */
    private <T, R> R commonCall(String type, Function<String, MKResultVO<T>> function, Function<MKResultVO<T>, R> resultMapper) {
        if (function == null) {
            log.warn("实际调用接口为空");
            return resultMapper.apply(null);
        }
        String token = this.getToken();
        if (token == null) {
            return resultMapper.apply(null);
        }
        try {
            MKResultVO<T> result = function.apply(token);
            if (result != null && result.isSuccess()) {
                return resultMapper.apply(result);
            }
            log.error("调用mk接口 {}异常响应参数：{}", type, JSONUtil.toJsonStr(result));
            String message = getMessage(result);
            throw new MKException("调用mk接口" + type + "异常 " + message);
        } catch (MKException e) {
            throw e;
        } catch (Exception e) {
            log.error("调用mk接口 {}异常", type, e);
            throw new MKException("调用mk接口" + type + "异常");
        }
    }

    /**
     *  获取消息
     * @param result
     * @return
     */
    private <T> String getMessage(MKResultVO<T> result) {
        return result == null ? "" : result.getMsg();
    }

    /**
     *  获取实际的标题，拼接配置的前缀
     * @param subject
     * @return
     */
    private String getActualSubject(String subject) {
        if (StringUtil.isBlank(subject)) {
            log.warn("调用mk接口 创建并提交流程标题为空");
            return subject;
        }
        String subjectPrefix = properties.getSubjectPrefix();
        if (StringUtil.isBlank(subjectPrefix)) {
            return subject;
        }
        return subjectPrefix + subject;
    }

    @Override
    public String getMKAccount(String mkCode) {
        if (!StringUtil.hasText(mkCode)) {
            log.warn("获取mk账号参数为空");
            return null;
        }
        String accessToken = getToken();
        MKOAuthTokenDTO tokenParam = new MKOAuthTokenDTO();
        tokenParam.setAppId(properties.getOauthAppId());
        tokenParam.setAppSecret(properties.getOauthAppSecret());
        tokenParam.setCode(mkCode);
        MKResultVO<MKOAuthTokenVO> tokenResult = client.getOAuthToken(tokenParam, accessToken);
        if (tokenResult == null || !tokenResult.isSuccess()) {
            log.error("调用mk接口 获取oauth token 异常：{}", JSONUtil.toJsonStr(tokenResult));
            return null;
        }
        MKOAuthTokenVO tokenVO = tokenResult.getData();
        if (tokenVO == null || tokenVO.getToken() == null) {
            log.error("获取oauth token 为空：{}", JSONUtil.toJsonStr(tokenResult));
            return null;
        }

        String token = tokenVO.getToken();
        MKResultVO<MKOAuthUserVO> userResult = client.getOAuthUserInfo(new MKOAuthUserDTO(token), accessToken);
        if (userResult == null || !userResult.isSuccess()) {
            log.error("调用mk接口 获取oauth 用户信息异常：{}", JSONUtil.toJsonStr(userResult));
            return null;
        }
        MKOAuthUserVO userVO = userResult.getData();
        if (userVO != null) {
            return userVO.getLoginName();
        }
        log.error("获取oauth 用户信息为空：{}", JSONUtil.toJsonStr(userResult));
        return null;
    }

    @Override
    public List<MKNodeVO> getCurrentNodes(String processInstanceId, String loginName) {
        String accessToken = getToken();
        MKCurrentNodesDTO param = new MKCurrentNodesDTO();
        param.setProcessInstanceId(processInstanceId);
        param.setLoginName(loginName);
        MKResultVO<List<MKNodeVO>> nodesResult = client.getCurrentNodes(param, accessToken);
        log.info("调用mk接口 获取流程当前节点详情 :{}", JSONUtil.toJsonStr(nodesResult));
        if (nodesResult == null || !nodesResult.isSuccess()) {
            log.error("调用mk接口 获取流程当前节点详情异常：{}", JSONUtil.toJsonStr(nodesResult));
            return null;
        }
        return nodesResult.getData();
    }

    @Override
    public MKAllHandlerVO getNodeHandlers(String processInstanceId, String loginName, String nodeId) {
        String accessToken = getToken();
        MKNodeHandlersDTO param = new MKNodeHandlersDTO();
        param.setProcessInstanceId(processInstanceId);
        param.setLoginName(loginName);
        param.setNodeId(nodeId);
        MKResultVO<MKAllHandlerVO> nodeHandlers = client.getNodeHandlers(param, accessToken);
        log.info("调用mk接口 获取节点处理人详情 :{}", JSONUtil.toJsonStr(nodeHandlers));
        if (nodeHandlers == null || !nodeHandlers.isSuccess()) {
            log.error("调用mk接口 获取节点处理人详情异常：{}", JSONUtil.toJsonStr(nodeHandlers));
            return null;
        }
        return nodeHandlers.getData();
    }

    @Override
    public boolean pushOrgDept(MKPushDTO<MKPushOrgDeptDTO> param) {
        String accessToken = getToken();
        MKResultVO<Object> resultVO = client.pushOrgDept(param, accessToken);
        log.info("调用mk接口 推送机构部门结果 :{}", JSONUtil.toJsonStr(resultVO));
        if (resultVO == null || !resultVO.isSuccess()) {
            log.error("调用mk接口 推送机构部门异常：{}", JSONUtil.toJsonStr(resultVO));
            return false;
        }
        return true;
    }

    @Override
    public boolean pushPerson(MKPushDTO<MKPushPersonDTO> param) {
        String accessToken = getToken();
        MKResultVO<Object> resultVO = client.pushPerson(param, accessToken);
        log.info("调用mk接口 推送人员结果 :{}", JSONUtil.toJsonStr(resultVO));
        if (resultVO == null || !resultVO.isSuccess()) {
            log.error("调用mk接口 推送人员异常：{}", JSONUtil.toJsonStr(resultVO));
            return false;
        }
        return true;
    }

    @Override
    public String getMKApprovalUrl(String processInstanceId, String loginName) {
        String accessToken = getToken();
        MKOAuthCodeDTO codeParam = new MKOAuthCodeDTO();
        codeParam.setAppId(properties.getOauthAppId());
        codeParam.setAppSecret(properties.getOauthAppSecret());
        codeParam.setLoginName(loginName);
        MKResultVO<MKOAuthCodeVO> codeResult = client.getOAuthCode(codeParam, accessToken);
        if (codeResult == null || !codeResult.isSuccess()) {
            log.error("调用mk接口 获取oauth 认证code 异常：{}", JSONUtil.toJsonStr(codeResult));
            String message = Optional.ofNullable(codeResult)
                    // 这个接口错误消息在 code，没有 msg
                    .map(MKResultVO::getCode)
                    .orElse(StringPool.EMPTY);
            throw new MKException(message);
        }
        MKOAuthCodeVO codeVO = codeResult.getData();
        if (codeVO == null || codeVO.getCode() == null) {
            log.error("获取oauth 认证code 为空：{}", JSONUtil.toJsonStr(codeVO));
            return null;
        }
        String code = codeVO.getCode();
        // 审批页面地址
        String approvalUrl = String.format(APPROVAL_URL_FORMAT, properties.getLoginUrl(), processInstanceId, processInstanceId);
        // sso回调地址
        return String.format(SSO_CALLBACK_URL_FORMAT, properties.getLoginUrl(), code, UrlUtil.encode(approvalUrl));
    }

    @Override
    public List<MKAuditNoteVO> queryAuditNotes(MKAuditNoteDTO param) {
        AssertUtils.notNull(param, "参数不能为空");
        AssertUtils.notBlank(param.getLoginName(),"登录名不能为空");
        AssertUtils.notBlank(param.getProcessInstanceId(),"流程实例id不能为空");
        List<MKAuditNoteVO> notes = commonCall("查询审批记录",
                token -> client.queryAuditNotes(param, token),
                result -> Optional.ofNullable(result)
                        .map(MKResultVO::getData)
                        .orElse(null));
        if (CollectionUtil.isEmpty(notes)) {
            return notes;
        }
        for (MKAuditNoteVO note : notes) {
            // 处理审批意见的附件全名
            handleFullName(note.getAttachmentParameter(), note.getMechanisms());
            if (CollectionUtil.isNotEmpty(note.getProcessComments())) {
                for (MKProcessCommentVO processComment : note.getProcessComments()) {
                    // 处理流程附言的附件全名
                    handleFullName(processComment.getAttachments(), processComment.getMechanisms());
                }
            }
        }
        return notes;
    }

    /**
     *  处理附件全名
     * @param attachments
     * @param mechanisms
     */
    private void handleFullName(List<MKAttachmentVO> attachments, MKMechanismsVO mechanisms) {
        if (CollectionUtil.isNotEmpty(attachments) && mechanisms != null && CollectionUtil.isNotEmpty(mechanisms.getAttachment())) {
            Map<String, String> fileIdFullNameMap = mechanisms.getAttachment().stream()
                    .collect(Collectors.toMap(MKMetaAttachmentVO::getId, MKMetaAttachmentVO::getFullName, (a, b) -> a));
            for (MKAttachmentVO attachment : attachments) {
                String fullName = fileIdFullNameMap.get(attachment.getFileId());
                if (StringUtil.isNotBlank(fullName)) {
                    // 附件名替换为全名
                    attachment.setName(fullName);
                }
            }
        }
    }

    @Override
    public List<MKSenderVO> querySenderList(MKSenderDTO param) {
        AssertUtils.notNull(param, "参数不能为空");
        AssertUtils.notBlank(param.getNodeInstanceId(),"节点实例id不能为空");
        AssertUtils.notBlank(param.getProcessInstanceId(),"流程实例id不能为空");
        return commonCall("查询抄送人列表", token -> {
            // 查询100，一次都查完
            param.setPageSize(100);
            return client.querySenderList(param, token);
        }, result -> Optional.ofNullable(result)
                .map(MKResultVO::getData)
                .map(MKPageVO::getContent)
                .orElse(Collections.emptyList()));
    }

    @Override
    public void downloadFile(HttpServletResponse response, String fileId) {
        AssertUtils.notBlank(fileId,"附件id不能为空");
        String accessToken = getToken();
        try (OutputStream outputStream = response.getOutputStream();) {
            try (Response feignResponse = client.downloadFile(new MKFileDTO(fileId), accessToken);) {
                InputStream inputStream = feignResponse.body().asInputStream();
                Map<String, Collection<String>> responseHeaders = feignResponse.headers();
                response.setHeader("Content-disposition", getHeader(responseHeaders, "Content-disposition"));
                // response.setHeader("Content-Type", getHeader(responseHeaders, "Content-Type"));
                response.setHeader("Content-Type", "image/png");
                IOUtils.copy(inputStream, outputStream);
            } catch (MKException e) {
                response.setHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE);
                outputStream.write(Optional.ofNullable(e.getMessage()).orElse("调用mk接口 下载附件异常").getBytes(StandardCharsets.UTF_8));
            } catch (Exception e) {
                log.error("调用mk接口 下载附件异常", e);
                response.setHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE);
                outputStream.write("调用mk接口 下载附件异常".getBytes(StandardCharsets.UTF_8));
            }
        } catch (Exception e) {
            log.error("下载mk附件异常", e);
        }

    }

    @Override
    public String getFileBase64(String fileId) {
        AssertUtils.notBlank(fileId,"附件id不能为空");
        String accessToken = getToken();
        try (Response feignResponse = client.downloadFile(new MKFileDTO(fileId), accessToken);) {
            InputStream inputStream = feignResponse.body().asInputStream();
            byte[] bytes = IOUtils.toByteArray(inputStream);
            return Base64.getEncoder().encodeToString(bytes);
        } catch (MKException e) {
            throw e;
        } catch (Exception e) {
            log.error("调用mk接口 下载附件异常", e);
        }
        return null;
    }

    @Override
    public String getNodeNumber(String templateId, String nodeId) {
        if (StringUtil.isBlank(templateId) || StringUtil.isBlank(nodeId)) {
            log.warn("查询节点编号模板id或节点id为空 模板id：{} 节点id：{}", templateId, nodeId);
            return null;
        }
        return commonCall("查询模板节点信息", token -> {
            return client.getManualNode(new MKManualNodeDTO(templateId), token);
        }, result -> Optional.ofNullable(result)
                .map(MKResultVO::getData)
                .flatMap(nodes -> nodes.stream()
                        .filter(node -> StringUtil.equals(node.getNodeId(), nodeId))
                        .findFirst()
                        .map(MKManualNodeVO::getNodeNumber)
                ).orElse(null));
    }

    @Override
    public MKPageVO<MKApprovalVO> queryApprovalList(MKApprovalDTO param) {
        AssertUtils.notNull(param, "参数不能为空");
        AssertUtils.notNull(param.getConditions(),"条件不能为空");
        AssertUtils.notNull(param.getConditions().getMydoc(),"类型不能为空");
        return commonCall("查询审批中心列表", token -> client.queryApprovalList(param, token),
                result -> Optional.ofNullable(result)
                .map(MKResultVO::getData)
                .orElse(MKPageVO.empty()));
    }

    @Override
    public MKPageVO<MKProcessVO> queryProcessList(MKProcessDTO param) {
        AssertUtils.notNull(param, "参数不能为空");
        AssertUtils.notBlank(param.getLoginName(),"登录名不能为空");
        AssertUtils.notNull(param.getMydoc(),"类型不能为空");
        return commonCall("查询流程列表", token -> client.queryProcessList(param, token),
                result -> Optional.ofNullable(result)
                        .map(MKResultVO::getData)
                        .orElse(MKPageVO.empty()));
    }

    /**
     *  获取响应头
     * @param responseHeaders
     * @param header
     * @return
     */
    private String getHeader(Map<String, Collection<String>> responseHeaders, String header) {
        Collection<String> collection = responseHeaders.get(header);
        if (CollectionUtil.isEmpty(collection)) {
            return null;
        }
        return collection.stream().findFirst().orElse(null);
    }

    /**
     * 获取token
     *
     * @return
     */
    private String getToken() {
        String token = bladeRedis.get(MKConstant.MK_TOKEN_KEY);
        if (token != null) {
            return token;
        }
        try {
            MKTokenDTO param = new MKTokenDTO();
            param.setAppKey(properties.getAppKey());
            param.setAppSecret(properties.getAppSecret());
            long start = System.currentTimeMillis();
            log.info("调用mk获取token开始");
            MKTokenVO tokenResult = client.getToken(param);
            long end = System.currentTimeMillis();
            log.info("调用mk获取token结束 耗时：{}", end - start);
            if (tokenResult != null && tokenResult.getErrCode() == MKConstant.TOKEN_SUCCESS_CODE) {
                // 成功
                String accessToken = tokenResult.getAccessToken();
                int expiresIn = tokenResult.getExpiresIn();
                // 过期时间 - 100秒，防止刚好过期
                bladeRedis.getValueOps().setIfAbsent(MKConstant.MK_TOKEN_KEY, accessToken, Duration.ofSeconds(expiresIn - 100));
                return accessToken;
            }
            log.error("调用mk接口 获取token 异常响应参数：{}", JSONUtil.toJsonStr(tokenResult));
            throw new MKException("调用mk接口获取token异常");
        } catch (MKException e) {
            throw e;
        } catch (Exception e) {
            log.error("调用mk接口 获取token 异常", e);
            throw new MKException("调用mk接口获取token异常");
        }
    }
}
