package com.ywt.outpatient.service.rpc.common;

import com.google.gson.JsonObject;
import com.ywt.biz.common.enums.TerminalEnum;
import com.ywt.biz.common.util.Checker;
import com.ywt.biz.common.util.DateUtil;
import com.ywt.biz.common.util.StringHelper;
import com.ywt.biz.common.util.serializers.JsonSerializer;
import com.ywt.gapi.message.SendTemplateMessageRequest;
import com.ywt.gapi.message.WeChatMessageService;
import com.ywt.outpatient.core.utils.AESUtil;
import com.ywt.outpatient.core.utils.BizUtil;
import com.ywt.outpatient.domain.entity.center.MedicalCard;
import com.ywt.outpatient.domain.entity.center.MedicalCardRepository;
import com.ywt.outpatient.domain.entity.center.SnsUserInfo;
import com.ywt.outpatient.domain.entity.center.SnsUserInfoRepository;
import com.ywt.outpatient.core.Constants;
import com.ywt.outpatient.domain.models.enums.MedicalInformationEnum;
import com.ywt.outpatient.domain.models.enums.RegisteredDatePeriod;
import com.ywt.outpatient.domain.wechat.Keyword;
import com.ywt.outpatient.service.rpc.BizConfigurer;
import org.apache.dubbo.config.annotation.DubboReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Service
public class WxProvider {
    private static Logger logger = LoggerFactory.getLogger(WxProvider.class);

    @Autowired
    private SnsUserInfoRepository snsUserInfoRepository;

    @Autowired
    private MedicalCardRepository medicalCardRepository;

    @DubboReference
    private WeChatMessageService weChatMessageServiceBlockingStub;

    @Autowired
    BizConfigurer bizConfigurer;

    private static int ERROR_STATUS = 1;

    /**
     * 太和医院，预约挂号成功通知
     * <p>
     * {{first.DATA}}
     * 就诊人：{{keyword1.DATA}}
     * 就诊卡号：{{keyword2.DATA}}
     * 挂号科室：{{keyword3.DATA}}
     * 看诊医生：{{keyword4.DATA}}
     * 就诊时间：{{keyword5.DATA}}
     * {{remark.DATA}}
     * <p>
     * 您好，您已预约成功！
     * 就诊人：张三
     * 就诊卡号：59562456
     * 挂号科室：产前门诊
     * 看诊医生：胡佳
     * 就诊时间：2017-09-08  11:00-12:00
     * 温馨提醒：1、就诊当天请提前在公众号报到候诊功能中进行取号，确认报到。
     * 2、未取号可在公众号上自行操作进行退号。
     * 3、取号后当天未能就诊，需到院进行退号处理。
     *
     * @param userId
     * @param patientName
     * @param hisPatientId
     * @param doctorName
     * @param registeredDate
     * @param deptName
     * @param startTime
     * @param endTime
     */
    public void notifyRegisteredTaihe(
            int userId, String patientName, String hisPatientId, String doctorName, Date registeredDate,
            String deptName, String startTime, String endTime, int hospitalId, int registeredPeriod) {
        MedicalCard medicalCard = medicalCardRepository.getCard(userId, hisPatientId);

        if (medicalCard == null) {
            return;
        }
        String url = "";
        String miniProgram = "";
        if (hospitalId == Constants.TAIHE_HOSPITAL_ID) {
            // 太和跳往公众号
            url = bizConfigurer.getYwtHttpsMsiteDomain() + Constants.TH_REGISTER_INFO_ORDER_CENTER_URL;
        } else if (hospitalId == Constants.NFYYBYFY_HOSPITAL_ID) {
            // 白云跳往小程序
            String pagePath = Constants.BY_REGISTER_INFO_ORDER_CENTER_LIST;
            miniProgram = BizUtil.getMiniProgramParam(Constants.NFYYBYFY_WXMP_APP_ID, pagePath);
        }
        final String tplCode = "TPL_1006";
        final String first = "您好，您已预约成功！";
        final String remark =
                "温馨提醒：" +
                        "1、就诊当天请提前在公众号报到候诊功能中进行取号，确认报到。" +
                        "2、未取号可在公众号上自行操作进行退号。" +
                        "3、取号后当天未能就诊，需到院进行退号处理。";
        String date = String.format("%s %s-%s",
                DateUtil.convertToString(registeredDate, "yyyy-MM-dd"), startTime, endTime);
        if (hospitalId == Constants.TAIHE_HOSPITAL_ID){
            date = String.format("%s %s (%s-%s)",
                    DateUtil.convertToString(registeredDate, "yyyy-MM-dd"),
                    RegisteredDatePeriod.getDisplayName(Checker.getIntegerValue(registeredPeriod)),
                    startTime, endTime);
        }
        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword(first));
        data.put("keyword1", new Keyword(patientName));
        data.put("keyword2", new Keyword(medicalCard.getCardNo()));
        data.put("keyword3", new Keyword(deptName));
        data.put("keyword4", new Keyword(doctorName));
        data.put("keyword5", new Keyword(date));
        data.put("remark", new Keyword(remark));

        sendTemplateMessage(userId, TerminalEnum.TaiheWxOfficial.getValue(), data, tplCode, url, miniProgram);
    }

    /**
     * 太和医院，取号成功通知
     * <p>
     * {{first.DATA}}
     * 当前排号：{{keyword1.DATA}}
     * 取号时间：{{keyword2.DATA}}
     * {{remark.DATA}}
     * <p>
     * 您好，取号成功！
     * 当前排号：12
     * 取号时间：2014-10-30 18:40
     * "
     *
     * @param userId
     * @param waitingNumber
     * @param date
     */
    public void notifyTookTaihe(int userId, int waitingNumber, Date date, String registrationId) {
        final String tplCode = "TPL_1005";
        final String url = String.format("https://m.ywtinfo.com/nfthwe/waitingTreatmentCount/%s", registrationId);
        final String miniProgram = "";
        final String first = "您好，取号成功！";
        final String strWaitingNumber = "--"; //String.valueOf(waitingNumber);
        final String remark = "";

        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword(first));
        data.put("keyword1", new Keyword(strWaitingNumber));
        data.put("keyword2", new Keyword(DateUtil.convertToString(date, "yyyy-MM-dd HH:mm")));
        data.put("remark", new Keyword(remark));

        sendTemplateMessage(userId, TerminalEnum.TaiheWxOfficial.getValue(), data, tplCode, url, miniProgram);
    }

    /**
     * 太和医院，预约取消成功
     * <p>
     * {{first.DATA}}
     * 就诊医院：{{keyword1.DATA}}
     * 就诊科室：{{keyword2.DATA}}
     * 就诊医生：{{keyword3.DATA}}
     * 就诊人：{{keyword4.DATA}}
     * 取消时间：{{keyword5.DATA}}
     * {{remark.DATA}}"
     * <p>
     * 您好，您已成功退号！
     * 就诊医院：南方医科大学南方医院
     * 就诊科室：儿科
     * 就诊医生：张三
     * 就诊人：李四
     * 取消时间：2017年6月26日 15:30
     *
     * @param userId
     */
    public void notifyCancelTaihe(int userId, String hospitalName, String deptName, String patientName, String doctorName, Date cancelDate) {
        final String tplCode = "TPL_1002";
        final String url = "";
        final String miniProgram = "";
        final String first = "您好，您已成功退号！";
        final String remark = "";

        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword(first));
        data.put("keyword1", new Keyword(hospitalName));
        data.put("keyword2", new Keyword(deptName));
        data.put("keyword3", new Keyword(doctorName));
        data.put("keyword4", new Keyword(patientName));
        data.put("keyword5", new Keyword(DateUtil.convertToString(cancelDate, "yyyy年MM月dd日 HH:mm")));
        data.put("remark", new Keyword(remark));

        sendTemplateMessage(userId, TerminalEnum.TaiheWxOfficial.getValue(), data, tplCode, url, miniProgram);
    }

    public void notifyCancel4NFYYBYFY(int userId, String hospitalName, String patientName, String doctorName, String regDateStr) {
        final String tplCode = "TPL_2006";
        final String url = "";
        final String miniProgram = "";
        final String first = "您好，您预约的号源已经成功取消。取消号源信息如下：";
        final String remark = "";

        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword(first));
        data.put("keynote1", new Keyword(patientName));
        data.put("keynote2", new Keyword(regDateStr));
        data.put("keynote3", new Keyword(doctorName));
        data.put("keynote4", new Keyword(hospitalName));
        data.put("remark", new Keyword(remark));

        sendTemplateMessage(userId, TerminalEnum.NFYYBYFY_WX_OFFICIAL.getValue(), data, tplCode, url, miniProgram);
    }

    private String getTplMsgCodeForPaidOP(Integer hospitalId) {
        switch (Checker.getIntegerValue(hospitalId)) {
            case Constants.TAIHE_HOSPITAL_ID:
                return "TPL_1003";
            case Constants.NFYYBYFY_HOSPITAL_ID:
                return "TPL_2004";
            default:
                return "";
        }
    }

    /**
     * 门诊医保缴费消息模板
     * @param hospitalId 医院 id
     */
    private String getTplMsgCodeForPaidOPYb(Integer hospitalId) {
        switch (Checker.getIntegerValue(hospitalId)) {
            case Constants.TAIHE_HOSPITAL_ID:
                return "TPL_008001";
            case Constants.NFYYBYFY_HOSPITAL_ID:
                return "TPL_021001";
            default:
                return "";
        }
    }

    private String getTplMsgCodeForOPPayFailed(Integer hospitalId) {
        switch (Checker.getIntegerValue(hospitalId)) {
            case Constants.TAIHE_HOSPITAL_ID:
                return "";// TODO: 太和的模板 id 还要确定
            case Constants.NFYYBYFY_HOSPITAL_ID:
                return "TPL_2010";
            default:
                return "";
        }
    }

    private boolean isTaihe(int hospitalId) {
        return hospitalId == Constants.TAIHE_HOSPITAL_ID;
    }

    private boolean isNFYYBYFY(int hospitalId) {
        return hospitalId == Constants.NFYYBYFY_HOSPITAL_ID;
    }

    public String getNfyybyfyMpParamString(String mpPath) {
        JsonObject object = new JsonObject();
        object.addProperty("appid", Constants.NFYYBYFY_WXMP_APP_ID);// 所需跳转到的小程序appid
        object.addProperty("pagepath", "pages/index/index?entry=" + getEncodedURI(mpPath));// 所需跳转到小程序的具体页面路径
        return object.toString();
    }

    private String getThNetMpParamString(String mpPath) {
        JsonObject object = new JsonObject();
        object.addProperty("appid", Constants.TH_PATIENT_WXMP_APP_ID);// 所需跳转到的小程序appid
        object.addProperty("pagepath", "pages/index/index?entry=" + getEncodedURI(mpPath));// 所需跳转到小程序的具体页面路径
        return object.toString();
    }

    private String getEncodedURI(String path) {
        try {
            return URLEncoder.encode(path, StandardCharsets.UTF_8.toString());
        } catch (UnsupportedEncodingException e) {
            logger.error("WxProvider#getEncodedURI(path={} ):\n {}", path, e.getMessage(), e);
            return path;
        }
    }

    /**
     * 太和医院，缴费成功通知
     * <p>
     * {{first.DATA}}
     * 用户姓名：{{keyword1.DATA}}
     * 诊疗卡号：{{keyword2.DATA}}
     * 订单号：{{keyword3.DATA}}
     * 缴费总金额：{{keyword4.DATA}}
     * 收费大项目费用：{{keyword5.DATA}}
     * {{remark.DATA}}
     * <p>
     * 您好，您已缴费成功
     * 用户姓名：张三
     * 诊疗卡号：000001
     * 订单号：83792817
     * 缴费总金额：300.00
     * 收费大项目费用：西药200.00，检验费100.00
     *
     * @param userId
     * @param patientName
     * @param orderNo
     * @param hisPatientId
     * @param individual
     */
    public void notifyOutpatientPaidTaihe(String hisOrderNo, int userId, String patientName, String orderNo,
                                          String hisPatientId, int individual, String invoiceNo, int hospitalId,
                                          boolean isMedIns, int totalAmount) {
        MedicalCard medicalCard = medicalCardRepository.getCard(userId, hisPatientId);

        if (medicalCard == null) {
            return;
        }

        final String tplCode = isMedIns ? getTplMsgCodeForPaidOPYb(hospitalId) : getTplMsgCodeForPaidOP(hospitalId);
//        final String url = String.format("https://m.ywtinfo.com/nfthwe/outpatientPay/detail/%s?filterType=2&hisPatientId=%s",
//                hisOrderNo, hisPatientId);
        final String url = isTaihe(hospitalId) ? String.format("https://m.ywtinfo.com/nfthwe/outpatientPay/treatGuide/%s", invoiceNo) : "";
        String mpPath = String.format("by/patient_dept_pay/detail?filterType=2&hisPatientId=%s&noEvent=1&invoiceNo=%s", hisPatientId, invoiceNo);
        final String miniProgram = isNFYYBYFY(hospitalId) ? getNfyybyfyMpParamString(mpPath) : "";
        final String first = "您好，您已缴费成功，请点击查看导诊指引。";
        final String desc = String.format("%.2f元", individual / 100d);
        final String item = String.format("处方费：%.2f元", individual / 100d);

        Map<String, Object> data = new HashMap<>();
        if (isMedIns) {
            data.put("first", new Keyword(first));
            data.put("keyword1", new Keyword(patientName));
            data.put("keyword2", new Keyword(orderNo)); // 订单号
            data.put("keyword3", new Keyword(String.format("%.2f元", totalAmount / 100d))); // 缴费金额
            data.put("keyword4", new Keyword(DateUtil.convertToString(new Date()))); // 缴费日期
            int medInsAmount = totalAmount - individual;
            if (medInsAmount < 0) medInsAmount = 0;
            data.put("remark", String.format("个人支付：￥%.2f\r\n医保报销：￥%.2f", (individual / 100d), (medInsAmount / 100d))); // 备注：个人支付，医保报销
        } else {
            data.put("first", new Keyword(first));
            data.put("keyword1", new Keyword(patientName));
            data.put("keyword2", new Keyword(medicalCard.getCardNo()));
            data.put("keyword3", new Keyword(orderNo));
            data.put("keyword4", new Keyword(desc));
            data.put("keyword5", new Keyword(item));
            data.put("remark", "");
        }

        sendTemplateMessage(userId, BizUtil.getGZHTerminalIdByHospitalId(hospitalId), data, tplCode, url, miniProgram);
    }

    /**
     * 门诊缴费异常消息
     */
    public void sendMsgWhenOPPayFailed(String paymentNo, int userId, String patientName, String deptName, String doctorName,
                                       String hisPatientId, int fee, String invoiceNo, int hospitalId, boolean isIns,
                                       String hisOrderNo) {
        final String tplCode = getTplMsgCodeForOPPayFailed(hospitalId);
        if (StringHelper.isNullOrEmpty(tplCode)) {
            logger.warn("hospitalId {} 没有配置消息代码", hospitalId);
            return;
        }
        final String url = isTaihe(hospitalId) ?
                String.format("https://m.ywtinfo.com/nfthwe/outpatientPay/detailYB/%s?filterType=&hisPatientId=%s&hosId=%d", hisOrderNo, hisPatientId, hospitalId)
                : "";
        String mpPath = String.format("by/patient_dept_pay/detailYb?invoiceNo=%s&hisPatientId=%s&noEvent=2&filterType=2&hosId=%d&status=%d&paymentNo=%s", invoiceNo, hisPatientId, hospitalId, ERROR_STATUS, paymentNo);
        final String miniProgram = isNFYYBYFY(hospitalId) ? getNfyybyfyMpParamString(mpPath) : "";
        final String first = "您本次缴费过程中出现异常，给您带来不便，深感抱歉！请您在门诊上班时间（周一至周五8：00-12：00、13：00-16：00，周六、日8：00-12：00，咨询电话：020-66230438）到门诊一号楼大堂收费处楼5号窗口，工作人员将帮您核实缴费情况，祝您健康！";
        final String feeStr = String.format("%.2f", fee / 100d);
        final String remark = String.format("支付流水号%s", paymentNo);

        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword(first));
        data.put("keyword1", new Keyword(BizUtil.getHospitalNameByIdSimply(hospitalId)));
        data.put("keyword2", new Keyword(patientName));
        data.put("keyword3", new Keyword(deptName));
        data.put("keyword4", new Keyword(doctorName));
        data.put("keyword5", new Keyword(feeStr));
        data.put("remark", new Keyword(remark));
        sendTemplateMessage(userId, BizUtil.getGZHTerminalIdByHospitalId(hospitalId), data, tplCode, url, miniProgram);
    }

     /**
     * 太和医院，门诊缴费提醒
     * <p>
     * {{first.DATA}}
     * 卡号：{{keyword1.DATA}}
     * 姓名：{{keyword2.DATA}}
     * 详情：{{keyword3.DATA}}
     * {{remark.DATA}}
     * <p>
     * 您好，该就诊卡在门诊存在待支付费用！
     * 卡号：0000000085
     * 姓名：黄汉森
     * 详情：你可以通过微信支付缴费或到人工窗口缴费！
     *
     * @param userId
     * @param cardNo
     * @param patientName
     */
    public void notifyOutpatientToPayTaihe(String hisOrderNo, int userId, String cardNo, String patientName, String hisPatientId) {
        final String tplCode = "TPL_1008";
        final String url = String.format("https://m.ywtinfo.com/nfthwe/outpatientPay/detail/%s?filterType=1&hisPatientId=%s",
                hisOrderNo, hisPatientId);
        final String miniProgram = "";
        final String first = "您好，该就诊卡在门诊存在待支付费用！";
        final String desc = "你可以通过微信支付缴费或到人工窗口缴费！";
        final String remark = "";

        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword(first));
        data.put("keyword1", new Keyword(cardNo));
        data.put("keyword2", new Keyword(patientName));
        data.put("keyword3", new Keyword(desc));
        data.put("remark", new Keyword(remark));

        sendTemplateMessage(userId, TerminalEnum.TaiheWxOfficial.getValue(), data, tplCode, url, miniProgram);
    }

    private String getTplMsgCodeForTake(Integer hospitalId) {
        switch (Checker.getIntegerValue(hospitalId)) {
            case Constants.TAIHE_HOSPITAL_ID:
                return "TPL_1009";
            case Constants.NFYYBYFY_HOSPITAL_ID:
                return "TPL_2002";
            default:
                return "";
        }
    }

    /**
     * 太和医院，取号提醒
     * <p>
     * 您好，您有一个取号提醒！
     * 就诊人：张三
     * 就诊卡号：123456
     * 就诊科室：神经外科
     * 接诊医生：李四
     * 就诊时间：2015-03-20 10:30-11:00
     * 请点击进入详情页面进行取号！
     * <p>
     * {{first.DATA}}
     * 就诊人：{{keyword1.DATA}}
     * 就诊卡号：{{keyword2.DATA}}
     * 就诊科室：{{keyword3.DATA}}
     * 接诊医生：{{keyword4.DATA}}
     * 就诊时间：{{keyword5.DATA}}
     * {{remark.DATA}}
     *
     * @param userId
     * @param patientName
     * @param hisPatientId
     * @param deptName
     * @param doctorName
     * @param registeredDate
     * @param startTime
     * @param endTime
     */
    public void notifyToTakeTaihe(int userId, String patientName, String hisPatientId, String deptName,
                                  String doctorName, Date registeredDate, String startTime, String endTime, int hospitalId) {
        MedicalCard medicalCard = medicalCardRepository.getCard(userId, hisPatientId);

        if (medicalCard == null) {
            return;
        }

        final String tplCode = getTplMsgCodeForTake(hospitalId);
        final String url = isTaihe(hospitalId) ? "https://m.ywtinfo.com/nfthwe/treatmentList" : "";
        final String miniProgram = isNFYYBYFY(hospitalId) ? getNfyybyfyMpParamString("by/wait_treatment/index") : "";
        final String first = "您好，您有一个取号提醒！";
        final String remark = "请点击进入详情页面进行取号！";
        final String date = String.format("%s %s-%s",
                DateUtil.convertToString(registeredDate, "yyyy-MM-dd"), startTime, endTime);

        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword(first));
        data.put("keyword1", new Keyword(patientName));
        data.put("keyword2", new Keyword(medicalCard.getCardNo()));
        // todo: 白云小程序，"就诊卡号"字段变成"患者ID"
        if (hospitalId == Constants.NFYYBYFY_HOSPITAL_ID){
            data.put("keyword2", new Keyword(medicalCard.getHisPatientId()));
        }
        data.put("keyword3", new Keyword(deptName));
        data.put("keyword4", new Keyword(doctorName));
        data.put("keyword5", new Keyword(date));
        data.put("remark", new Keyword(remark));

        sendTemplateMessage(userId, BizUtil.getGZHTerminalIdByHospitalId(hospitalId), data, tplCode, url, miniProgram);
    }


    private String getTplMsgCodeForNotifyExpired(Integer hospitalId) {
        switch (Checker.getIntegerValue(hospitalId)) {
            case Constants.TAIHE_HOSPITAL_ID:
                return "TPL_1007";
            case Constants.NFYYBYFY_HOSPITAL_ID:
                return "TPL_2003";
            default:
                return "";
        }
    }

    /**
     * 太和医院，就诊时间过期通知
     * <p>
     * 你好，就诊时间已过，你未能按时就诊，且超过了退号的有效时间，该号源已过期作废。
     * 就诊人：王大宝
     * 就诊卡类型：诊疗卡
     * 卡号：8888 8888 8888
     * 就诊医院：北京医院
     * 就诊科室：内科
     * <p>
     * {{first.DATA}}
     * 就诊人：{{keyword1.DATA}}
     * 就诊卡类型：{{keyword2.DATA}}
     * 卡号：{{keyword3.DATA}}
     * 就诊医院：{{keyword4.DATA}}
     * 就诊科室：{{keyword5.DATA}}
     * {{remark.DATA}}
     *
     * @param userId
     * @param patientName
     * @param hisPatientId
     * @param deptName
     */
    public void notifyExpiredTaihe(int userId, String patientName, String hisPatientId, String deptName, int hospitalId) {
        MedicalCard medicalCard = medicalCardRepository.getCard(userId, hisPatientId);

        if (medicalCard == null) {
            return;
        }

        final String tplCode = getTplMsgCodeForNotifyExpired(hospitalId);
        final String url = "";
        final String miniProgram = "";
        final String first = "你好，就诊时间已过，你未能按时就诊，且超过了退号的有效时间，该号源已过期作废。";
        final String remark = "";

        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword(first));
        data.put("keyword1", new Keyword(patientName));
        data.put("keyword2", new Keyword("诊疗卡"));
        data.put("keyword3", new Keyword(medicalCard.getCardNo()));
        data.put("keyword4", new Keyword(BizUtil.getHospitalNameByIdSimply(hospitalId)));
        data.put("keyword5", new Keyword(deptName));
        data.put("remark", new Keyword(remark));

        sendTemplateMessage(userId, BizUtil.getGZHTerminalIdByHospitalId(hospitalId), data, tplCode, url, miniProgram);
    }


    private String getTplMsgCodeForInspection(Integer hospitalId) {
        switch (Checker.getIntegerValue(hospitalId)) {
            case Constants.TAIHE_HOSPITAL_ID:
                return "TPL_1010";
            case Constants.NFYYBYFY_HOSPITAL_ID:
                return "TPL_2005";
            default:
                return "";
        }
    }

    /**
     * 太和医院，检验检查结果提醒
     * <p>
     * 你好，你有一条检验检查报告结果。
     * 姓名：陆雪琪
     * 项目名称：血常规
     * 就诊时间：2014年7月21日 18:36
     * 请点击查看。
     * <p>
     * {{first.DATA}}
     * 姓名：{{keyword1.DATA}}
     * 项目名称：{{keyword2.DATA}}
     * 就诊时间：{{keyword3.DATA}}
     * {{remark.DATA}}
     *
     * @param userId
     * @param inspectionTitle
     * @param date
     */
    public void notifyInspectionTaihe(int userId, String patientName, String inspectionTitle, Date date, String url,
                                      int hospitalId, String miniProgram) {
        final String tplCode = getTplMsgCodeForInspection(hospitalId);
        final String first = "你好，你有一条检验检查报告结果。";
        final String remark = "请点击查看。";

        if (isNFYYBYFY(hospitalId)) {
            url = "";
            miniProgram = getNfyybyfyMpParamString(miniProgram);
        }
        if (isTaihe(hospitalId)) miniProgram = "";

        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword(first));
        data.put("keyword1", new Keyword(patientName));
        data.put("keyword2", new Keyword(inspectionTitle));
        data.put("keyword3", new Keyword(DateUtil.convertToString(date, "yyyy年MM月dd日")));
        data.put("remark", new Keyword(remark));

        sendTemplateMessage(userId, BizUtil.getGZHTerminalIdByHospitalId(hospitalId), data, tplCode, url, miniProgram);
    }

    /**
     * 太和医院，加号通知
     * <p>
     * 您好，加号成功！
     * 医生：张海峰
     * 职称：副主任医师
     * 医院：南方医科大学南方医院
     * 庹伶俐，您好！您已成功加号2017-06-12 08:06中医妇科门诊张海峰副主任医师号，请在就诊当天带上身份证到南方医科大学南方医院人工窗口取号以及支付，如过时将作延迟就诊处理。如当次未就诊记爽约一次，爽约三次将对预约进行限制。如无法就诊请提前一天取消预约。祝您身体健康！
     * <p>
     * {{first.DATA}}
     * 医生：{{keyword1.DATA}}
     * 职称：{{keyword2.DATA}}
     * 医院：{{keyword3.DATA}}
     * {{remark.DATA}}
     *
     * @param userId
     * @param doctorName
     * @param doctorTitle
     * @param hospitalName
     * @param patientName
     * @param startTime
     * @param deptName
     */
    public void notifyAddRegisteredTaihe(
            int userId, String doctorName, String doctorTitle, String hospitalName, String patientName,
            Date registeredDate, String startTime, String deptName) {
        final String tplCode = "TPL_1001";
        final String url = "";
        final String miniProgram = "";
        final String first = "您好，加号成功！";
        final String remark = String.format("%s，您好！您已成功加号%s %s号，" +
                        "请在就诊当天带上身份证到南方医科大学南方医院人工窗口取号以及支付，如过时将作延迟就诊处理。" +
                        "如当次未就诊记爽约一次，爽约三次将对预约进行限制。如无法就诊请提前一天取消预约。祝您身体健康！",
                patientName,
                DateUtil.convertToString(registeredDate, "yyyy-MM-dd"),
                startTime + deptName + doctorName + doctorTitle);

        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword(first));
        data.put("keyword1", new Keyword(doctorName));
        data.put("keyword2", new Keyword(doctorTitle));
        data.put("keyword3", new Keyword(hospitalName));
        data.put("remark", new Keyword(remark));

        sendTemplateMessage(userId, TerminalEnum.TaiheWxOfficial.getValue(), data, tplCode, url, miniProgram);
    }

    /**
     * 太和医院，加号取消成功通知
     *
     * @param userId
     * @param doctorName
     * @param doctorTitle
     */
    public void notifyCancelAdditionalTaihe(int userId, String doctorName, String doctorTitle) {
        final String first = "您好，加号取消成功！";
        final String hospitalName = "南方医科大学南方医院";

        notifyAddRegisteredTaihe(userId, first, doctorName, doctorTitle, hospitalName, "", "");
    }

    /**
     * 太和医院，医生给患者加号
     *
     * @param userId
     * @param doctorName
     * @param doctorTitle
     * @param cardNo
     */
    public void notifyGetAdditionalTaihe(int userId, String doctorName, String doctorTitle, String cardNo) {
        final String first = String.format("您好，%s医生为您（诊疗卡：%s）申请了加号权限，请点击进入加号预约！", doctorName, cardNo);
        final String hospitalName = "南方医院太和分院";
        final String url = "https://m.ywtinfo.com/nfthwe/plusOrder";

        notifyAddRegisteredTaihe(userId, first, doctorName, doctorTitle, hospitalName, "", url);
    }

    /**
     * 太和医院，模板ID:  0d8KiMDkS4xjEgbGIM14oZtWfl_CocOfaHM9U19l3rI
     * <p>
     * 模板内容：
     * {{first.DATA}}
     * 医生：{{keyword1.DATA}}
     * 职称：{{keyword2.DATA}}
     * 医院：{{keyword3.DATA}}
     * {{remark.DATA}}
     * <p>
     * 示例：
     * 您好，加号成功！
     * 医生：张海峰
     * 职称：副主任医师
     * 医院：南方医科大学南方医院
     * 庹伶俐，您好！您已成功加号2017-06-12 08:06中医妇科门诊张海峰副主任医师号，
     * 请在就诊当天带上身份证到南方医科大学南方医院人工窗口取号以及支付，如过时将作延迟就诊处理。
     * 如当次未就诊记爽约一次，爽约三次将对预约进行限制。如无法就诊请提前一天取消预约。祝您身体健康！
     *
     * @param userId
     * @param first
     * @param keyword1
     * @param keyword2
     * @param keyword3
     * @param remark
     * @param url
     */
    public void notifyAddRegisteredTaihe(
            int userId, String first, String keyword1, String keyword2, String keyword3, String remark, String url) {
        final String tplCode = "TPL_1001";
        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword(first));
        data.put("keyword1", new Keyword(keyword1));
        data.put("keyword2", new Keyword(keyword2));
        data.put("keyword3", new Keyword(keyword3));
        data.put("remark", new Keyword(remark));

        sendTemplateMessage(userId, TerminalEnum.TaiheWxOfficial.getValue(), data, tplCode, url, "");
    }

    /**
     * 发送微信模板消息
     *
     * @param userId
     * @param terminal
     * @param data
     * @param tplCode
     * @param url
     * @param miniProgram
     */
    private void sendTemplateMessage(
            int userId, int terminal, Map<String, Object> data, String tplCode, String url, String miniProgram) {
        SnsUserInfo snsUserInfo = snsUserInfoRepository.getSnsUserInfoByUserId(userId, terminal);

        if (snsUserInfo == null || snsUserInfo.getDeleted() || StringHelper.isNullOrWhiteSpace(snsUserInfo.getOpenId())) {
            return;
        }

        sendTemplateMessage(snsUserInfo.getOpenId(), data, tplCode, url, miniProgram);
    }

    /**
     * 发送微信模板消息
     *
     * @param openId
     * @param data
     * @param tplCode
     * @param url
     * @param miniProgram
     */
    private void sendTemplateMessage(
            String openId, Map<String, Object> data, String tplCode, String url, String miniProgram) {
        try {
            SendTemplateMessageRequest req = SendTemplateMessageRequest.newBuilder()
                    .setData(JsonSerializer.toJson(data))
                    .setTemplateCode(tplCode)
                    .setUrl(url)
                    .setMiniProgram(miniProgram)
                    .setToUser(openId)
                    .build();
            weChatMessageServiceBlockingStub.sendTemplateMessage(req);
        } catch (Exception e) {
            logger.error("sendTemplateMessage(): {}", e.getMessage(), e);
        }
    }


    /**
     * 医生认证-认证成功通知，模板ID:  xkfERGNrpRHKCvv5-YREs5kUjLKGBtmlT8gHSO6GiWE
     * <p>
     * 模板内容：
     * "{{first.DATA}}
     * 认证类型：{{keyword1.DATA}}
     * 审核结果：{{keyword2.DATA}}
     * 审核时间：{{keyword3.DATA}}
     * {{remark.DATA}}"
     * <p>
     * 示例：
     * "您好，您的医生认证已通过。
     * 认证类型：医生认证
     * 审核结果：认证成功
     * 审核时间：2016/05/05 11:20
     * 您现在已经可以使用公众号内所有功能了！"
     *
     * @param userId
     */
    public void notifyDoctorVerityPassDoctor(int userId) {
        final String tplCode = "TPL_1011";
        final String url = bizConfigurer.getYwtHttpsMsiteDomain() + "/doctorwe/";
        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword("您好，您的医生认证已通过。"));
        data.put("keyword1", new Keyword("医生认证"));
        data.put("keyword2", new Keyword("认证成功"));
        data.put("keyword3", new Keyword(DateUtil.convertToString(new Date(), "yyyy-MM-dd HH:mm")));
        data.put("remark", new Keyword("您现在已经可以使用公众号内所有功能了！"));

        sendTemplateMessage(userId, TerminalEnum.DoctorWxOfficial.getValue(), data, tplCode, url, "");
    }

    /**
     * 医生认证-认证失败通知，模板ID:  FR0VSOPNXUxPR9CeNoCgawL_1QfEGrwgFbsmFlm9oUw
     * <p>
     * 模板内容：
     * "{{first.DATA}}{{fi
     * 审核时间：{{keyword1.DATA}}
     * 认证状态：{{keyword2.DATA}}
     * 失败原因：{{keyword3.DATA}}
     * {{remark.DATA}}"
     * <p>
     * 示例：
     * "您好，您的医生认证未能被通过。
     * 审核时间：1月2日  20:11
     * 认证状态：医生认证不通过
     * 失败原因：（推送后台医生认证的“备注”中管理员填写的消息)
     * 请根据提示内容修改并重新认证。"
     *
     * @param userId
     */
    public void notifyDoctorVerityNoPassDoctor(int userId, String remark) {
        final String tplCode = "TPL_1012";
        final String url = bizConfigurer.getYwtHttpsMsiteDomain() + "/doctorwe/editInfo";
        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword("您好，您的医生认证未能被通过。"));
        data.put("keyword1", new Keyword(DateUtil.convertToString(new Date(), "yyyy-MM-dd HH:mm")));
        data.put("keyword2", new Keyword("医生认证不通过"));
        data.put("keyword3", new Keyword(remark));

        data.put("remark", new Keyword("请根据提示内容修改并重新认证。"));

        sendTemplateMessage(userId, TerminalEnum.DoctorWxOfficial.getValue(), data, tplCode, url, "");
    }


    /**
     * 药事快讯-文献传递状态更新通知，模板ID:  ERDYWAABuSpDdSxk6LxHziKPvWaK969u6RxcfKTBi3s
     * <p>
     * 模板内容：
     * "{{first.DATA}}
     * 文献标题：{{keyword1.DATA}}
     * 申请时间：{{keyword2.DATA}}
     * {{remark.DATA}}"
     * <p>
     * 示例：
     * "您好，您有新的药事快讯。
     * 文献标题：(推送的药事快讯文章名)
     * 申请时间：2014-07-21 18:36：01
     * 请前往“我的工作站”的“药事快讯”查看。"
     *
     * @param userId
     */
    public void notifyArtDoctor(int userId, String titleName, int type, int infoId) {
        final String tplCode = "TPL_1013";
//        String url = getDrugNewsDetailUrl(TerminalEnum.DoctorWxOfficial.getValue(), userId, infoId, type);
        // 产品优化2提出修改: 点击模板消息，进入药事快讯页面不进入具体文章。edited on 2018-08-17 14:15:06 by Walker
        String url = String.format("%s/doctorwe/drugNews", bizConfigurer.getYwtHttpsMsiteDomain());
        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword("您好，您有新的药事快讯。"));
        data.put("keyword1", new Keyword(titleName));
        data.put("keyword2", new Keyword(DateUtil.convertToString(new Date(), "yyyy-MM-dd HH:mm")));

        data.put("remark", new Keyword("请前往“我的工作站”的“药事快讯”查看。"));

        sendTemplateMessage(userId, TerminalEnum.DoctorWxOfficial.getValue(), data, tplCode, url, "");
    }

    /**
     * 南方医务通发送医生提醒给患者
     * <h3>模板格式：</h3>
     * <p>{{first.DATA}}</p>
     * <p>用户姓名：{{keyword1.DATA}}</p>
     * <p>提醒类型：{{keyword2.DATA}}</p>
     * <p>提醒时间：{{keyword3.DATA}}</p>
     * <p>{{remark.DATA}}</p>
     * <p></p>
     * <h3>示例：</h3>
     * <p>您好！您的医生提醒您看一份资料！</p>
     * <p>用户姓名：王丽（姓名）</p>
     * <p>提醒类型：（资料标题）</p>
     * <p>提醒时间：2017年11月8日 10:11</p>
     *
     * @param userId    用户id
     * @param userName  用户姓名
     * @param infoTitle 资料标题
     * @param type      资料类型 参考{@link com.ywt.outpatient.domain.models.enums.MedicalInformationEnum}
     * @param infoId    资料id
     * @param doctorId  医生id
     */
    public void notifyNewInfoForYwt(int userId, String userName, String infoTitle, int type, int infoId, int doctorId) {
        notifyNewInfo(TerminalEnum.PatientWxOfficial.getValue(), userId, userName, infoTitle, type, infoId, doctorId);
    }

    /**
     * 太和发送医生提醒给患者
     * <h3>模板格式：</h3>
     * <p>{{first.DATA}}</p>
     * <p>用户姓名：{{keyword1.DATA}}</p>
     * <p>提醒类型：{{keyword2.DATA}}</p>
     * <p>提醒时间：{{keyword3.DATA}}</p>
     * <p>{{remark.DATA}}</p>
     * <p></p>
     * <h3>示例：</h3>
     * <p>您好！您的医生提醒您看一份资料！</p>
     * <p>用户姓名：王丽（姓名）</p>
     * <p>提醒类型：（资料标题）</p>
     * <p>提醒时间：2017年11月8日 10:11</p>
     *
     * @param userId    用户id
     * @param userName  用户姓名
     * @param infoTitle 资料标题
     * @param type      资料类型 参考{@link com.ywt.outpatient.domain.models.enums.MedicalInformationEnum}
     * @param infoId    资料id
     * @param doctorId  医生id
     */
    public void notifyNewInfoForTaihe(int userId, String userName, String infoTitle, int type, int infoId, int doctorId) {
        notifyNewInfo(TerminalEnum.TaiheWxOfficial.getValue(), userId, userName, infoTitle, type, infoId, doctorId);
    }

    /**
     * 发送医生提醒给患者
     *
     * @param clientType 客户端类型，参考{@link TerminalEnum}
     * @param userId     用户id
     * @param userName   用户姓名
     * @param infoTitle  资料标题
     * @param type       资料类型 参考{@link com.ywt.outpatient.domain.models.enums.MedicalInformationEnum}
     * @param infoId     资料id
     * @param doctorId   医生id
     */
    private void notifyNewInfo(int clientType, int userId, String userName, String infoTitle, int type, int infoId, int doctorId) {
        final String tplCode = "TPL_1014";
        String url = getDrugNewsDetailUrl(clientType, doctorId, infoId, type);
        Map<String, Object> map = new HashMap<>();
        map.put("first", new Keyword("您好！您的医生提醒您看一份资料！"));
        map.put("keyword1", new Keyword(userName));
        map.put("keyword2", new Keyword(infoTitle));
        map.put("keyword3", new Keyword(DateUtil.convertToString(new Date(), "yyyy-MM-dd HH:mm")));
        sendTemplateMessage(userId, clientType, map, tplCode, url, "");
    }

    /**
     * 格式化药事咨询详情跳转url
     *
     * @param clientType 客户端类型，参考{@link TerminalEnum}
     * @param doctorId   医生id
     * @param infoId     资讯id
     * @param infoType   资讯类型 参考{@link com.ywt.outpatient.domain.models.enums.MedicalInformationEnum}
     * @return formatted url
     */
    private String getDrugNewsDetailUrl(int clientType, int doctorId, int infoId, int infoType) {
        String patternUrl = "";
        switch (TerminalEnum.getTerminalEnum(clientType)) {
            case DoctorWxOfficial: // 医生端
                patternUrl = "%s/doctorwe/drugNews/detail/%d/%d";
                break;
            case PatientWxOfficial: // 患者端
                patternUrl = "%s/nfywtwe/drugNews/detail/%d/%d";
                break;
            case TaiheWxOfficial: // 太和
                patternUrl = "%s/nfthwe/drugNews/detail/%d/%d";
                break;
        }
        String url = "";
        switch (MedicalInformationEnum.getMedicalInformation(infoType)) {
            case ARTICLE:
                url = String.format(patternUrl, bizConfigurer.getYwtHttpsMsiteDomain(), doctorId, infoId);
                break;
            case VIDEO:
                url = String.format(patternUrl, bizConfigurer.getYwtHttpsMsiteDomain(), doctorId, infoId) + "?type=video";
                break;
            case PDF:
                url = String.format(patternUrl, bizConfigurer.getYwtHttpsMsiteDomain(), doctorId, infoId) + "?type=pdf";
                break;
        }
        return url;
    }

    /**
     * 核酸检测缴费后推送通知到相关公众号
     */
    public void sendWxMsgAfterNatPaid(int hospitalId, String patientName, String orderNo, int amount, String paymentCode,
                                      int userId, int orderId) {
        String tplCode = getTplMsgCodeForPaidOP(hospitalId);
        String mpPath = String.format("qrcodes/nucleic_acid/index?orderId=%d", orderId);
        final String miniProgram = isNFYYBYFY(hospitalId) ? getNfyybyfyMpParamString(mpPath) : getThNetMpParamString(mpPath);
        final String first = "您好！您已缴费成功，请点击查看详情。";
        final String desc = String.format("缴费总金额：%.2f元", amount / 100d);
        final String remark = String.format("缴费码：%s", paymentCode);

        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword(first));
        data.put("keyword1", new Keyword(patientName));
        data.put("keyword2", new Keyword("-"));
        data.put("keyword3", new Keyword(orderNo));
        data.put("keyword4", new Keyword(desc));
        data.put("remark", new Keyword(remark));
        int terminal = BizUtil.getGZHTerminalIdByHospitalId(hospitalId);
        logger.error("WxProvider#sendWxMsgAfterNatPaid(hospitalId={} , terminal={} , userId={} , orderId={} ):\n",
                hospitalId, terminal, userId, orderId);
        sendTemplateMessage(userId, terminal, data, tplCode, "", miniProgram);
    }

    public void sendCheckupBooking (int userId, String orderNo, int hospitalId, String medCardNo, String patientIdNo, String patientName,
                                    String deptName, String date, String time, String hisPatientId) {
        final String tplCode = getTplMsgCode(hospitalId);
        String patientIdNoEncrypt = AESUtil.encrypt(patientIdNo);
        String mpPath = String.format(Constants.BY_CHECKUP_BOOKING_ORDER_DETAIL_URL, orderNo, hospitalId, patientIdNoEncrypt);
        final String miniProgram = isNFYYBYFY(hospitalId) ? getNfyybyfyMpParamString(mpPath) : getThNetMpParamString(mpPath);
        final String url = "";
        final String first = "您好，您已成功预约体检！";
        final String remark = "1、静脉采血、腹部超声检查及呼气试验检查结果后方可用餐。\r\n" +
                "2、全部项目检查结束后，请您务必将指引单交还前台，以便为您出具检查报告。\r\n" +
                "3、请您妥善保管好个人随身携带的财物。\r\n" +
                "4、请在体检登记日期15个工作日内完成检查项目，如未完成，系统将自动取消，后果自负。";
        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword(first));
        data.put("keyword1", new Keyword(patientName));
        data.put("keyword2", new Keyword(medCardNo));
        // todo: 白云小程序，"就诊卡号"字段变成"患者ID"
        if (hospitalId == Constants.NFYYBYFY_HOSPITAL_ID){
            data.put("keyword2", new Keyword(hisPatientId));
        }
        data.put("keyword3", new Keyword(deptName));
        data.put("keyword4", new Keyword("-"));
        data.put("keyword5", new Keyword(date + " " + time));
        data.put("remark", new Keyword(remark));
        int terminal = BizUtil.getGZHTerminalIdByHospitalId(hospitalId);
        sendTemplateMessage(userId, terminal, data, tplCode, url, miniProgram);
    }


    private String getTplMsgCode(Integer hospitalId) {
        switch (Checker.getIntegerValue(hospitalId)) {
            case Constants.NFYYBYFY_HOSPITAL_ID:
                return "TPL_2001";
            // TODO: 待定
            case Constants.TAIHE_HOSPITAL_ID:
            default:
                return "";
        }
    }

    //病案邮寄
    public String getMedicalRecordOrderPath(int orderId, int hospitalId) {
        String path = String.format(Constants.BY_MEDICAL_RECORD_MAILING_ORDER_DETAIL_URL, orderId);
        return hospitalId == Constants.NFYYBYFY_HOSPITAL_ID ? getNfyybyfyMpParamString(path) : getThNetMpParamString(path);
    }

    public void sendOperationSchedule(Integer hospitalId, int userId, String patientName, String hisPatientId, String eventName,
                                      String operRoomNo) {
        final String tplCode = getTplOpMsgCode(hospitalId);
        String mpPath = String.format(Constants.BY_OPERATION_SCHEDULE, hospitalId, hisPatientId);
        final String miniProgram = isNFYYBYFY(hospitalId) ? getNfyybyfyMpParamString(mpPath) : getThNetMpParamString(mpPath);
        final String first = "手术进度";
        final String remark = "请您知悉。";
        Map<String, Object> data = new HashMap<>();
        data.put("first", new Keyword(first));
        data.put("keyword1", new Keyword(patientName));
        data.put("keyword2", new Keyword(operRoomNo));
        data.put("keyword3", new Keyword(eventName));
        data.put("remark", new Keyword(remark));
        int terminal = BizUtil.getGZHTerminalIdByHospitalId(hospitalId);
        sendTemplateMessage(userId, terminal, data, tplCode, "", miniProgram);
    }

    private String getTplOpMsgCode(Integer hospitalId) {
        switch (Checker.getIntegerValue(hospitalId)) {
            case Constants.NFYYBYFY_HOSPITAL_ID:
                return "TPL_2021";
            // TODO: 待定
            case Constants.TAIHE_HOSPITAL_ID:
            default:
                return "";
        }
    }

}
