package thyyxxk.webserver.service.ca;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.IdcardUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.dtflys.forest.Forest;
import com.dtflys.forest.http.ForestRequest;
import com.dtflys.forest.logging.LogConfiguration;
import com.dtflys.forest.utils.ForestDataType;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import thyyxxk.webserver.config.Assertion;
import thyyxxk.webserver.config.envionment.ArchiveConfig;
import thyyxxk.webserver.config.envionment.CaData;
import thyyxxk.webserver.config.exception.BizException;
import thyyxxk.webserver.config.exception.ExceptionEnum;
import thyyxxk.webserver.constants.YesOrNo;
import thyyxxk.webserver.dao.his.ca.PatientIdCardTypeDao;
import thyyxxk.webserver.dao.his.outpatient.thmz.MzEmrDao;
import thyyxxk.webserver.dao.his.zhuyuanyisheng.emr.EmrPatientDao;
import thyyxxk.webserver.entity.ResultVo;
import thyyxxk.webserver.entity.ca.*;
import thyyxxk.webserver.entity.login.UserInfo;
import thyyxxk.webserver.entity.outpatient.thmz.MzEmrPatientData;
import thyyxxk.webserver.entity.zhuyuanyisheng.emr.EmrPatientData;
import thyyxxk.webserver.entity.zhuyuanyisheng.emr.SignCompleteEnum;
import thyyxxk.webserver.service.hutoolcache.CaCache;
import thyyxxk.webserver.service.hutoolcache.UserCache;
import thyyxxk.webserver.service.zhuyuanyisheng.emr.EmrServer;
import thyyxxk.webserver.utils.ResultVoUtil;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Slf4j
@Service
public class CaServer implements Assertion {
    private final CaData caData;
    private final String SUCCRSS = "success";
    private final EmrPatientDao emrPatientDao;
    private final String archivePath;
    private final static String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKAc+jzqVKHsjNdyuDGL7u6zZR55j87l+0GGT4/yEV+K8ZtL1GoIgOD6vv6eEg2iOwl6Wh6hq8XR4cIyzVyvMZ35dIw3oGN89ObtuCha7gLOmWYUIGVnkUBkLFeIdwe4F8bq1q15o7azsTVekSyNJf/wnxWMp8ibtvO41T8DIzZPAgMBAAECgYAeeLVVD4Gw2VyKij4sy8Var1CQxrDMPu+c7ktJCVguFzrZA8r2rQyeBIqB2aJ07smOibcJ+lm/Ca0N8O4tc/gcm4g/JsjBALFUmqyCkLQpRyIbz8V9uAmrLcchT74GS727Uv4KVTmQNDZTPIDdSZWSJ8S+WKmj8q3wUF1HLF1eXQJBAOC7D6xIB2XNS6Voa7C7m8aeg53lfcDUVJ6m0YFdvMbsU2cnlxnCSX1OsghFuVQLKcGeN+aXiTEileb3pAl2RC0CQQC2ZDtDb+1OmLmRNugp29Pmm8MM8KpktfqiBFi92Q747XwEeMgLi3qetq1wDT4dgvyURs2McsbF7diVtFv7DMXrAkAoYzXj3mYF86k+ps+DyZOrVF2PCOlauE4k3RIVz8TXcy1iAolzRalzbastNWqjIgZ1F3wwYtdzDyYlhifi03BZAkEArkgidPMbwDGhiAf+WhkrZz1JaTECsM9PGcergGVLsENFcQR0qstxtPz7x4lv5EVI0urA+Man93OptIsuJTr0VwJAd86EbHyOn5Lah7ptPGsmsqkrdMZcJ3chRj6pDtvb0A2lhaJfDRxmAiWCnviPhTApH6/1WRzBqc1ZTymyRTF9kA==";
    private final static String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCgHPo86lSh7IzXcrgxi+7us2UeeY/O5ftBhk+P8hFfivGbS9RqCIDg+r7+nhINojsJeloeoavF0eHCMs1crzGd+XSMN6BjfPTm7bgoWu4CzplmFCBlZ5FAZCxXiHcHuBfG6tateaO2s7E1XpEsjSX/8J8VjKfIm7bzuNU/AyM2TwIDAQAB";
    private final CaCache cache;
    private final UserCache userCache;
    private final MzEmrDao mzEmrDao;

    private final PatientIdCardTypeDao patientIdCardTypeDao;

    private final static String TEMP = "temp";

    RSA idCardRsa = new RSA(privateKey, publicKey);


    public enum CaReturnEnum {
        zy, mz
    }

    public CaServer(CaData caData, EmrPatientDao emrPatientDao, CaCache cache, UserCache userCache, ArchiveConfig archive, MzEmrDao mzEmrDao, PatientIdCardTypeDao patientIdCardTypeDao) {
        this.caData = caData;
        this.emrPatientDao = emrPatientDao;
        this.archivePath = archive.getPath() + "/archive/emr";
        this.cache = cache;
        this.userCache = userCache;
        this.mzEmrDao = mzEmrDao;
        this.patientIdCardTypeDao = patientIdCardTypeDao;
    }

    private String getRedisKey(String documentId) {
        return "ca-emr-" + documentId;
    }

    public List<CaReturn.CaData> sendBatchByCode(CaSignClass.Send send) {
        List<CaReturn.CaData> returnValue = new ArrayList<>();
        send.setCount(send.getCount() == null ? 1 : send.getCount());
        for (int i = 0; i < send.getCount(); i++) {
            returnValue.add(sendByCode(send));
        }
        return returnValue;
    }

    public CaReturn.CaData sendByCode(CaSignClass.Send send) {
        UserInfo userInfoByCode = userCache.get(send.getId());
        send.setBizSn(IdUtil.simpleUUID());
        String msg = CaUtils.GetSHA256FormString(send.getMsg());
        CaReturn push = push(send, msg);
        signatureAuthentication(msg, push);
        String timeStamp = generateTimestampByInData(msg);
        push.getData().setTimeStamp(timeStamp);
        push.getData().setBizSn(send.getBizSn());
        push.getData().setSignature("/doctorSignatureImage/" + send.getId() + ".png");
        push.getData().setName(userInfoByCode.getName());
        push.getData().setMsg(msg);
        push.getData().setCode(send.getId());
        return push.getData();
    }

    /**
     * 推送用户签名
     *
     * @param send
     * @return 返回值
     */
    private CaReturn push(CaSignClass.Send send, String msg) {
        CaData.MobileApp app = caData.getMobileApp();

        CaSendParams params = CaSendParams
                .builder()
                .appId(app.getAppId())
                .id(send.getId())
                .bizSn(send.getBizSn())
                .msg(msg)
                .msgWrapper("0")
                .desc(URLUtil.encode(send.getDesc()))
                .url(URLUtil.encode("http://127.0.0.1"))
                .mode("redirect")
                .sign(CaUtils.getSign(app.getPrivatekey(), msg))
                .build();

        String execute = Forest
                .post(app.getUrl() + "/v1/push/sign")
                .bodyType(ForestDataType.FORM)
                .addBody(params).execute(String.class);

        String decode = URLUtil.decode(execute);

        log.info("返回数据：{}", decode);

        CaReturn caReturn = JSONUtil.toBean(decode, CaReturn.class);

        if (!SUCCRSS.equals(caReturn.getRet())) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "签名失败。" + caReturn.getMsg());
        }
        if (Integer.valueOf(1).equals(caReturn.getData().getIsTrust())) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "签名失败，请用户打开免密签名。");
        }
        if (Integer.valueOf(2).equals(caReturn.getData().getIsTrust())) {
            return caReturn;
        }
        throw new BizException(ExceptionEnum.LOGICAL_ERROR, "签名失败。" + caReturn.getMsg());
    }

    /**
     * 认证签名
     *
     * @param msg      消息
     * @param caReturn
     */
    private void signatureAuthentication(String msg, CaReturn caReturn) {
        CaData.SignAuthentication signData = caData.getSignAuthentication();
        CaWSDL caWSDL = new CaWSDL(signData.getUrl());
        String replace = getVerifySignDataParams(msg, caReturn, signData);
        JSONObject rst = caWSDL.invoke("verifySignData", replace);
        log.info("认证签名xml：{}\n认证签名返回值:{}\n", replace, rst.toString());
        Integer code = rst.getByPath("Root.RetCode", Integer.class);
        if (!YesOrNo.YES.getCode().equals(code)) {
            String rstmsg = rst.getByPath("Root.RetMsg", String.class);
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, rstmsg);
        }
    }

    private static @NotNull String getVerifySignDataParams(String msg, CaReturn caReturn, CaData.SignAuthentication signData) {
        String request = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
                         "<Root>\n" +
                         "\t<AppCode>${AppCode}</AppCode>\n" +
                         "\t<AppPWD>${AppPWD}</AppPWD>\n" +
                         "\t<Request>\n" +
                         "\t\t<Cert>${Cert}</Cert>\n" +
                         "\t\t<SignAlg>${SignAlg}</SignAlg>\n" +
                         "\t\t<InData>${InData}</InData>\n" +
                         "\t\t<SignData>${SignData}</SignData>\n" +
                         "\t</Request>\n" +
                         "</Root>";

        return request.replace("${AppCode}", signData.getAppCode())
                .replace("${AppPWD}", signData.getAppPwd())
                .replace("${Cert}", caReturn.getData().getCert())
                .replace("${SignAlg}", signData.getSignAig())
                .replace("${InData}", msg)
                .replace("${SignData}", caReturn.getData().getSignValue());
    }

    /**
     * 认证时间戳并返回
     *
     * @param msg 数据
     * @return
     */
    private String generateTimestampByInData(String msg) {
        String data = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
                      "<Root>\n" +
                      "\t<AppCode>${AppCode}</AppCode>\n" +
                      "\t<AppPWD>${AppPWD}</AppPWD>\n" +
                      "\t<Request>\n" +
                      "\t\t<InData>${InData}</InData>\n" +
                      "<HashAlg>${HashAlg}</HashAlg>\n" +
                      "<CertReq>${CertReq}</CertReq>\n" +
                      "\t</Request>\n" +
                      "</Root>";

        CaData.TimestampAuthentication timeData = caData.getTimestampAuthentication();
        String request = data.replace("${AppCode}", timeData.getAppCode())
                .replace("${AppPWD}", timeData.getAppPwd())
                .replace("${InData}", msg)
                .replace("${HashAlg}", "SHA256")
                .replace("${CertReq}", "true");

        CaWSDL caWSDL = new CaWSDL(timeData.getUrl());
        JSONObject ret = caWSDL.invoke("generateTimestampByInData", request);
        Integer code = ret.getByPath("Root.RetCode", Integer.class);
        log.info("认证时间戳xml：{}\n认证时间戳返回值:{}\n", request, ret);
        if (code != 1) {
            String retmsg = ret.getByPath("Root.RetMsg", String.class);
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, retmsg);
        }
        return ret.getByPath("Root.Response.TimeStamp", String.class);
    }

    public ResultVo<CaSingBizSnReturnData> getSignInfoByBizSn(String bizSn) {
        CaData.MobileApp app = caData.getMobileApp();
        ForestRequest<?> post = Forest
                .post(app.getUrl() + "/v1/sign/info/" + bizSn)
                .setLogConfiguration(new LogConfiguration() {{
                    setLogEnabled(false);
                }})
                .bodyType(ForestDataType.JSON);
        CaSendParams send = new CaSendParams();
        send.setAppId(app.getAppId());

        String indata = "appId=" + app.getAppId() + "&bizSn=" + bizSn;
        String sign = CaUtils.getSign(app.getPrivatekey(), indata);
        send.setSign(sign);

        String ret = post.addBody(send).execute(String.class);

        CaSingBizSnReturnData execute = JSONUtil.toBean(ret, CaSingBizSnReturnData.class);
        if (SUCCRSS.equals(execute.getRet())) {
            CaSingBizSnReturnData.SingData data = execute.getData();
            try {
                data.setSignData(URLDecoder.decode(data.getSignData(), "UTF-8"));
                data.setCert(URLDecoder.decode(data.getCert(), "UTF-8"));
                data.setSeal(URLDecoder.decode(data.getSeal(), "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                log.error(e.getMessage());
                return ResultVoUtil.success(ExceptionEnum.LOGICAL_ERROR, "解码失败请联系管理员" + bizSn);
            }
            return ResultVoUtil.success(execute);
        }
        return ResultVoUtil.success(ExceptionEnum.LOGICAL_ERROR, execute.getMsg());
    }


    private void verifyMedicalRecordStatus(String documentId, EmrSignType type) {
        EmrPatientData emrData = emrPatientDao.selectById(documentId);
        if (emrData == null || emrData.getDelFlag() == 1) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "病历已被删除。");
        }
        isTrueErr(SignCompleteEnum.COMPLETE.getCode().equals(emrData.getSignComplete()), "签名流程已完成请勿重复点击");
        if (emrData.getSignType() != null && !Objects.equals(type.getCode(), emrData.getSignType())) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "请使用上次选择的方式完成CA流程中途不得修改。");
        } else {
            emrPatientDao.updateById(EmrPatientData.builder()
                    .emrDocumentId(documentId)
                    .signType(type.getCode())
                    .build());
        }
    }

    /**
     * 发送签名到移动平板
     *
     * @param moreEventSign 数据
     * @return 暂无
     */
    public ResultVo<JSONObject> sendMoreEventSign(CaSignClass.MoreEventSign moreEventSign) {
        if (moreEventSign.getIsInpatientNo()) {
            verifyMedicalRecordStatus(moreEventSign.getDocumentId(), EmrSignType.MOVE);
        }
        moreEventSign.getData().forEach(item -> {
            isBlank(item.getIdCard(), "身份证不能为空。");
            isBlank(item.getSignName(), "签名人名字不能为空。");
            isBlank(item.getSignType(), "与患者的关系不能为空。");
            item.setIdCard(idCardRsa.decryptStr(item.getIdCard(), KeyType.PrivateKey));
        });
        String redisKey = getRedisKey(moreEventSign.getDocumentId());
        String redisData = Convert.toStr(cache.get(redisKey), null);

        if (redisData != null) {
            cancelSignature(redisData);
        }

        cache.del(redisKey);

        String uuid = IdUtil.simpleUUID();
        JSONObject data = getMoreEventSignData(moreEventSign, uuid);
        String execute = Forest.post(caData.getHBoardSign().getUrl() + "/mobile/hBoardSign")
                .addBody(data)
                .bodyType(ForestDataType.JSON)
                .execute(String.class);
        JSONObject rst = JSONUtil.parseObj(execute);
        String msg = rst.getByPath("ret_msg", String.class);
        String decode = URLUtil.decode(msg);
        log.info("返回值：{}，\n错误信息：{}", execute, decode);
        if (rst.getInt("ret_code") == 0) {
            // 设置病历的签名状态为发送到了ca接口，如果是住院就触发
            if (moreEventSign.getIsInpatientNo()) {
                setEmrSignComplete(moreEventSign.getDocumentId(), SignCompleteEnum.SEND_CA_API);
            }
            cache.put(redisKey, uuid);
            return ResultVoUtil.success(ExceptionEnum.SUCCESS_AND_EL_MESSAGE, "发送成功。");
        } else {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "请联系管理员" + decode);
        }
    }

    private JSONObject getMoreEventSignData(CaSignClass.MoreEventSign moreEventSign, String uuid) {
        JSONObject ret = new JSONObject();
        CaData.HBoardSign app = caData.getHBoardSign();
        ret.set("api_key", app.getApiKey());
        ret.set("api_secret", app.getApiSecret());
        JSONObject data = new JSONObject();
        data.set("document_no", uuid);
        data.set("pdf", EmrServer.getPdfBase(moreEventSign.getDocumentId()));
        data.set("return_url", URLUtil.encode(app.getReturnUrl() + "/" +
                                              (moreEventSign.getIsInpatientNo() ? CaReturnEnum.zy.toString() :
                                                      CaReturnEnum.mz.toString()) + "/"
                                              + moreEventSign.getDocumentId()));

        JSONArray signature = new JSONArray();

        for (int i = 0; i < moreEventSign.getData().size(); i++) {
            CaSignClass.MoreEventSignData item = moreEventSign.getData().get(i);
            JSONObject entries = new JSONObject();
            entries.set("type", "keyword");
            entries.set("sign_type", URLUtil.encode(item.getSignTypeName()));
            entries.set("keyword", new JSONObject() {{
                set("page", "0");
                set("content", URLUtil.encode(item.getContent()));
            }});
            JSONObject user_info = new JSONObject() {{
                set("name", item.getSignName());
                set("idno", item.getIdCard());
                set("age", IdcardUtil.getAgeByIdCard(item.getIdCard()));
            }};
            user_info.set("relation", item.getSignType());

            // 签名意见
            if (StrUtil.isNotBlank(item.getSignOpinion())) {
                JSONObject informed_keyword = JSONUtil.createObj()
                        .set("informed_page", 0)
                        .set("informed_content", URLUtil.encode(item.getSignOpinion()));

                JSONObject informed_stamp = JSONUtil.createObj()
                        .set("informed_tip", URLUtil.encode("签名意见"))
                        .set("informed_num", i + 1)
                        .set("informed_keyword", informed_keyword);
                entries.set("informed_stamp", JSONUtil.createArray().set(informed_stamp));
            }
            entries.set("user_info", user_info);
            signature.put(entries);
        }

        // 签署信息
        data.set("signature", signature);
        data.set("doctor_info", new JSONObject() {{
            set("doctor_no", moreEventSign.getSendCode());
        }});

        data.set("pic_size", new JSONObject() {{
            set("width", "100");
            set("height", "15");
        }});

        ret.set("data", data);
        return ret;
    }

    /**
     * h5 签名返回一个http连接，现在没啥用，可能以后能用到
     *
     * @param value 参数
     * @return 数据
     */
    public ResultVo<CaSignClass.H5Return> h5EventSign(CaSignClass.H5EventSign value) {
        JSONObject ret = new JSONObject();
        String uuid = IdUtil.simpleUUID();
        CaData.HBoardSign app = caData.getHBoardSign();
        ret.set("api_key", app.getApiKey());
        ret.set("api_secret", app.getApiSecret());
        ret.set("data", new JSONObject() {{
            set("document_no", uuid);
            set("pdf", EmrServer.getPdfBase(value.getDocumentId()));
            set("type", "keyword");
            set("keyword", new JSONObject() {{
                set("page", "0");
                set("content", URLUtil.encode(value.getContent()));
            }});
        }});
        String execute = Forest
                .post(caData.getHBoardSign().getUrl() + "/seal/h5EventSign")
                .addBody(ret)
                .bodyType(ForestDataType.JSON)
                .execute(String.class);

        return getStringResultVo(execute, uuid, "");
    }

    @Getter
    public enum EmrSignType {

        QR(1, "扫码"),
        MOVE(2, "移动平板"),
        HANDWRITING_BOARD(3, "有线手写板");

        private final Integer code;
        private final String name;

        EmrSignType(int code, String name) {
            this.code = code;
            this.name = name;
        }
    }

    /**
     * CA 扫码签名
     *
     * @param value
     * @return
     */
    public ResultVo<CaSignClass.H5Return> hBoardSignV2(CaSignClass.H5EventSign value) {
        isNoll(value.getSignType(), "请选择和患者的关系");
        isBlank(value.getIdCard(), "身份证不能为空。");
        isBlank(value.getName(), "姓名不能为空。");
        JSONObject ret = new JSONObject();
        String caDocumentId = IdUtil.simpleUUID();
        verifyMedicalRecordStatus(value.getDocumentId(), EmrSignType.QR);

        CaSignClass.H5EventSignRedis redisData = clearHBoardSignV2Data(value);

        String idCard = idCardRsa.decryptStr(value.getIdCard(), KeyType.PrivateKey);
        ret.set("api_key", caData.getHBoardSign().getApiKey());
        ret.set("api_secret", caData.getHBoardSign().getApiSecret());
        ret.set("data", new JSONObject() {{
            set("document_no", caDocumentId);
            set("pdf", redisData.getPdf());
            set("sign_way", "face,hand_sign");
            set("keyword", new JSONObject() {{
                set("page", "0");
                set("content", URLUtil.encode(value.getContent()));
            }});
            set("user_info", new JSONObject() {{
                set("name", URLUtil.encode(value.getName()));
                set("idno", idCard);
            }});
            set("collection_source", "face_photos,hand_sign");
            if (StrUtil.isNotBlank(value.getSignOpinion())) {
                set("informed_stamp", new JSONObject() {{
                    set("informed_type", "informed_keyword");
                    set("informed_keyword", new JSONObject() {{
                        set("informed_page", "0");
                        set("informed_content", URLUtil.encode(value.getSignOpinion()));
                    }});
                }});
            }
        }});

        String execute = Forest
                .post(caData.getHBoardSign().getUrl() + "/v2/seal/hBoardSign")
                .addBody(ret)
                .bodyType(ForestDataType.JSON)
                .execute(String.class);

        redisData.setUuid(value.getUuid());
        cache.put("ca-emr-" + value.getDocumentId(), JSONUtil.toJsonStr(redisData));
        return getStringResultVo(execute, caDocumentId, value.getUuid());
    }

    private CaSignClass.H5EventSignRedis clearHBoardSignV2Data(CaSignClass.H5EventSign value) {
        CaSignClass.H5EventSignRedis redisData = new CaSignClass.H5EventSignRedis();
        String redisKey = getRedisKey(value.getDocumentId());
        // 如果 uuid 是空的就是第一次
        if (StrUtil.isBlank(value.getUuid())) {
            value.setUuid(IdUtil.simpleUUID());
            cache.del(redisKey);
            redisData.setPdf(EmrServer.getPdfBase(value.getDocumentId()));
        } else {
            String redisStr = cache.get(redisKey);
            if (redisStr == null) {
                throw new BizException(ExceptionEnum.LOGICAL_ERROR, "签名超时请重新签名。");
            }
            redisData = JSONUtil.toBean(redisStr, CaSignClass.H5EventSignRedis.class);

            if (!redisData.getUuid().equals(value.getUuid())) {
                throw new BizException(ExceptionEnum.LOGICAL_ERROR, "有其他人正在签署，本次签名失效。");
            }
        }
        return redisData;
    }

    @NotNull
    private ResultVo<CaSignClass.H5Return> getStringResultVo(String execute,
                                                             String caDocumentId,
                                                             String uuid) {
        JSONObject rst = JSONUtil.parseObj(execute);
        String msg = URLUtil.decode(rst.getByPath("ret_msg", String.class));
        if (0 == rst.getByPath("ret_code", Integer.class)) {
            CaSignClass.H5Return build = CaSignClass.H5Return.builder()
                    .result(rst.getByPath("sign_url", String.class))
                    .caDocumentId(caDocumentId)
                    .uuid(uuid)
                    .build();
            return ResultVoUtil.success(build);
        }
        return ResultVoUtil.fail(ExceptionEnum.NO_DATA_EXIST, "签名失败" + msg);
    }

    public ResultVo<JSONObject> downloadSealV2(String id, String documentId) {
        JSONObject ret = new JSONObject();
        ret.set("api_key", caData.getHBoardSign().getApiKey());
        ret.set("api_secret", caData.getHBoardSign().getApiSecret());
        ret.set("data", new JSONObject() {{
            set("document_no", id);
        }});
        String execute = Forest
                .post(caData.getHBoardSign().getUrl() + "/v2/seal/download")
                .addBody(ret)
                .bodyType(ForestDataType.JSON)
                .execute(String.class);
        JSONObject rst = JSONUtil.parseObj(execute);
        String msg = URLUtil.decode(rst.getByPath("ret_msg", String.class));
        if (0 == rst.getByPath("ret_code", Integer.class)) {
            return ResultVoUtil.success(rst);
        }
        return ResultVoUtil.fail(ExceptionEnum.NO_DATA_EXIST, msg);
    }

    /**
     * 判断扫码是否完成以及回写扫码的数据
     *
     * @return 提示
     */
    public ResultVo<Boolean> scanCodeVerification(String id, String documentId, String uuid) {
        ResultVo<JSONObject> res = downloadSealV2(id, documentId);
        if (!res.getCode().equals(ExceptionEnum.SUCCESS.getCode())) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, res.getMessage());
        }
        JSONObject rst = res.getData();
        String key = getRedisKey(documentId);
        String redisStr = cache.get(key);
        if (redisStr == null) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "签名错误，文书不存在。");
        }


        CaSignClass.H5EventSignRedis redisData = JSONUtil.toBean(redisStr, CaSignClass.H5EventSignRedis.class);

        if (!redisData.getUuid().equals(uuid)) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "有其他人正在签署，本次签名失效。");
        }

        redisData.setPdf(rst.getByPath("pdf", String.class));
        JSONObject saveData = new JSONObject();
        saveData.set("hand_sign", rst.get("hand_sign"));
        saveData.set("fingerprint", rst.get("hand_sign"));
        saveData.set("informed_img", rst.get("hand_sign"));
        saveData.set("face_photo", rst.get("hand_sign"));
        saveData.set("biz_sn", id);
        if (null == redisData.getData()) {
            redisData.setData(new ArrayList<>());
        }
        redisData.getData().add(saveData);
        cache.put(key, JSON.toJSONString(redisData));
        return ResultVoUtil.success(true);
    }


    /**
     * 扫码流程完成
     */
    public void completeQrCode(String documentId) {
        EmrPatientData data = emrPatientDao.selectById(documentId);
        String redisKey = getRedisKey(documentId);

        String redisStr = cache.get(redisKey);

        if (redisStr == null) {
            throw new BizException(ExceptionEnum.ERROR_MESSAGE, "文件不存在");
        }

        CaSignClass.H5EventSignRedis redisServerData = JSONUtil.toBean(redisStr, CaSignClass.H5EventSignRedis.class);
        CaSignClass.Upload upload = CaSignClass.Upload.builder()
                .file(redisServerData.getPdf())
                .fileName(documentId)
                .patNo(data.getPatNo())
                .times(data.getTimes())
                .caData(JSONUtil.toJsonStr(redisServerData))
                .build();

        uploadBase64(upload);
        EmrPatientData build = EmrPatientData
                .builder()
                .emrDocumentId(documentId)
                .signComplete(SignCompleteEnum.COMPLETE.getCode())
                .build();
        emrPatientDao.updateById(build);
        cache.del(redisKey);
    }

    public void cancelSignature(String id) {
        JSONObject ret = new JSONObject();
        CaData.HBoardSign app = caData.getHBoardSign();
        ret.set("api_key", app.getApiKey());
        ret.set("api_secret", app.getApiSecret());
        ret.set("data", new JSONObject() {{
            set("document_no", id);
        }});

        Forest.post(caData.getHBoardSign().getUrl() + "/v2/seal/cancelSigne")
                .addBody(ret)
                .bodyType(ForestDataType.JSON)
                .execute(JSONObject.class);
    }

    /**
     * 签名回调
     *
     * @param document 文档id
     * @param data     签名数据
     * @return
     */
    public JSONObject hBoardSignReturnUrl(String document, JSONObject data) {
        log.info("住院签名回调：{}，数据：{}", document, data);
        EmrPatientData emrData = emrPatientDao.selectById(document);
        String redisKey = getRedisKey(document);
        String redisData = cache.get(redisKey);

        if (emrData == null || redisData == null) {
            return R(-1, "病历文档不存在");
        }

        JSONObject signData = appDownload(redisData);

        CaSignClass.Upload build = CaSignClass.Upload
                .builder()
                .patNo(emrData.getPatNo())
                .times(emrData.getTimes())
                .file(signData.getStr("pdf"))
                .fileName(document)
                .caData(signData.getStr("signed"))
                .build();

        uploadBase64(build);

        EmrPatientData update = EmrPatientData.builder()
                .emrDocumentId(document)
                .signComplete(SignCompleteEnum.COMPLETE.getCode())
                .build();
        cache.del(redisKey);
        emrPatientDao.updateById(update);
        return R(1, "success");
    }

    public JSONObject mzBoardSignReturnUrl(String document, JSONObject data) {
        log.info("门诊签名回调：{}，数据：{}", document, data);
        MzEmrPatientData emrData = mzEmrDao.selectById(document);
        String redisKey = getRedisKey(document);
        String redisData = cache.get(redisKey);
        if (emrData == null || redisData == null) {
            return R(-1, "病历文档不存在");
        }
        JSONObject signData = appDownload(redisData);
        CaSignClass.Upload build = CaSignClass.Upload
                .builder()
                .patNo(emrData.getPatNo())
                .times(emrData.getTimes())
                .file(signData.getStr("pdf"))
                .fileName(document)
                .caData(signData.getStr("signed"))
                .build();
        uploadBase64(build);

        MzEmrPatientData update = MzEmrPatientData.builder()
                .emrDocumentId(document)
                .signComplete(true).build();
        mzEmrDao.updateById(update);
        return R(1, "success");
    }

    public JSONObject R(Integer code, String msg) {
        return new JSONObject() {{
            set("ret_code", code);
            set("res_msg", msg);
        }};
    }

    public JSONObject appDownload(String bizSn) {
        JSONObject data = new JSONObject() {{
            set("api_key", caData.getHBoardSign().getApiKey());
            set("api_secret", caData.getHBoardSign().getApiSecret());
            set("data", new JSONObject() {{
                set("document_no", bizSn);
            }});
        }};

        JSONObject execute = Forest
                .post(caData.getHBoardSign().getUrl() + "/app/download")
                .bodyType(ForestDataType.JSON)
                .addBody(data.toJSONString(0))
                .execute(JSONObject.class);

        execute.set("ret_msg", URLUtil.decode(execute.getStr("ret_msg")));
        log.info("返回:{}", execute);

        if (execute.getInt("ret_code") != 0) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, execute.getStr("ret_msg"));
        }
        return execute;
    }

    public String uploadBase64(CaSignClass.Upload upload) {
        byte[] file = Base64.decode(upload.getFile());
        String tmpPath = "/" + upload.getPatNo()
                         + "/" + upload.getTimes()
                         + "/emr"
                         + "/" + upload.getFileName();
        String pdfPath = tmpPath + ".pdf";
        String caPath = "/" + upload.getPatNo()
                        + "/" + upload.getTimes()
                        + "/ca/"
                        + upload.getFileName()
                        + ".txt";

        // 写入CA数据
        FileUtil.writeString(upload.getCaData(), archivePath + caPath, StandardCharsets.UTF_8);
        FileUtil.writeBytes(file, archivePath + pdfPath);
        return "/archive" + pdfPath;
    }

    public ResultVo<List<PatientIdCardType>> getIdCardTypeList(String patNo) {
        List<PatientIdCardType> idCardTypeList = patientIdCardTypeDao.getIdCardTypeList(patNo);
        PatientIdCardType patInfo = patientIdCardTypeDao.getPatientIdCard(patNo);

        if (idCardTypeList == null) {
            idCardTypeList = new ArrayList<>();
        }

        if (StrUtil.isNotBlank(patInfo.getIdCard())) {
            idCardTypeList.add(0,
                    PatientIdCardType.builder()
                            .patNo(patNo)
                            .idCard(patInfo.getIdCard())
                            .id(TEMP)
                            .name(patInfo.getName())
                            .caType(1)
                            .build()
            );
        }

        return ResultVoUtil.success(idCardTypeList);
    }

    @Data
    public static class SavePatientIdCardType {
        private List<PatientIdCardType> insert;
        private List<PatientIdCardType> update;
        private String patNo;
    }


    public ResultVo<String> savePatientIdCardType(SavePatientIdCardType data) {
        if (data == null) {
            return ResultVoUtil.success();
        }

        data.getInsert().forEach(item -> {
            item.setId(IdUtil.fastSimpleUUID());
            item.setPatNo(data.getPatNo());
            if (StrUtil.isBlank(item.getIdCard())) {
                throw new BizException(ExceptionEnum.NULL_POINTER, "身份证不能为空");
            }
            if (StrUtil.isBlank(item.getName())) {
                throw new BizException(ExceptionEnum.NULL_POINTER, "名字不能为空");
            }

            patientIdCardTypeDao.insert(item);
        });

        data.getUpdate().forEach(item -> {
            if (TEMP.equals(item.getId())) {
                return;
            }
            if (StrUtil.isBlank(item.getIdCard())) {
                throw new BizException(ExceptionEnum.NULL_POINTER, "身份证不能为空");
            }
            if (StrUtil.isBlank(item.getName())) {
                throw new BizException(ExceptionEnum.NULL_POINTER, "名字不能为空");
            }
            item.setPatNo(data.getPatNo());
            patientIdCardTypeDao.updateById(item);
        });
        return ResultVoUtil.success(ExceptionEnum.SUCCESS_AND_EL_NOTIFICATION);
    }


    public ResultVo<String> deletePatientIdCardType(String id) {
        // 判断传入的id是否为TEMP
        if (TEMP.equals(id)) {
            // 如果是，则返回失败信息
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "无法删除患者本人的信息");
        }
        // 根据id删除患者身份证类型
        patientIdCardTypeDao.deleteById(id);
        // 返回成功信息
        return ResultVoUtil.success(ExceptionEnum.SUCCESS_AND_EL_NOTIFICATION);
    }

    public ResultVo<String> sendMedicalRecordsToTablet(String id) {
        // 根据id查询病历数据
        EmrPatientData emrPatientData = emrPatientDao.selectById(id);

        // 如果病历数据为空，则返回失败信息
        if (emrPatientData == null) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "病历不存在");
        }

        // 如果病历状态不为INIT，则返回失败信息
        if (!SignCompleteEnum.INIT.getCode().equals(emrPatientData.getSignComplete())) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, StrUtil.format("此病历状态为：{}，无法再次发起签名", SignCompleteEnum.getDescriptionByCode(emrPatientData.getSignComplete())));
        }
        setEmrSignComplete(emrPatientData.getEmrDocumentId(), SignCompleteEnum.SEND_MORE);
        // 返回成功信息
        return ResultVoUtil.success(ExceptionEnum.SUCCESS_AND_EL_MESSAGE);
    }

    /**
     * 设置病历签名完成状态
     *
     * @param documentId 病历文档ID
     * @param value      签名完成状态
     */
    public void setEmrSignComplete(String documentId, SignCompleteEnum value) {
        // 构建更新病历数据对象
        EmrPatientData update = EmrPatientData.builder()
                .emrDocumentId(documentId) // 设置病历文档ID
                .signComplete(value.getCode()) // 设置病历签名完成状态
                .build();
        // 更新病历数据
        emrPatientDao.updateById(update);
    }


}
