package thyyxxk.webserver.service.inpatient;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import thyyxxk.webserver.config.Assertion;
import thyyxxk.webserver.config.exception.BizException;
import thyyxxk.webserver.config.exception.ExceptionEnum;
import thyyxxk.webserver.dao.his.inpatient.HospitalizationCostsDao;
import thyyxxk.webserver.dao.his.inpatient.xmlr.XiangMuLuRuDao;
import thyyxxk.webserver.entity.ResultVo;
import thyyxxk.webserver.entity.inpatient.inpatientcost.ZyDetailChargeTable;
import thyyxxk.webserver.entity.inpatient.inpatientcost.ZyDetailRefundParam;
import thyyxxk.webserver.entity.inpatient.ZyActpatient;
import thyyxxk.webserver.entity.login.UserInfo;
import thyyxxk.webserver.service.PublicServer;
import thyyxxk.webserver.service.hutoolcache.UserCache;
import thyyxxk.webserver.utils.*;

import java.math.BigDecimal;
import java.util.*;

/**
 * 项目录入退费
 */
@Service
@Slf4j
public class HospitalizationCostsService implements Assertion {
    private final HospitalizationCostsDao dao;
    private final XiangMuLuRuDao luRuDao;
    private final PublicServer publicServer;
    private final UserCache userCache;

    @Data
    public static class OrderInfo {
        private String classCode;
        private String drugClass;
        private String miniUnit;
        /**
         * 是否可以退费 0 - 否 1- 是
         */
        private Integer refundableFees;
    }

    @Data
    public static class RefundParam {
        private String patNo;
        private Integer times;
        /**
         * ledgerSn 和 detailSn 加起来 使用下划线
         */
        private List<String> key;
        private List<ZyDetailChargeTable> feeList;

        public void setFeeList(List<ZyDetailChargeTable> list) {
            feeList = list;
            if (ListUtil.notBlank(list)) {
                if (key == null) {
                    key = new ArrayList<>();
                }
                for (ZyDetailChargeTable item : list) {
                    key.add(item.getLedgerSn() + "_" + item.getDetailSn());
                }
            }
        }
    }

    @Getter
    public enum RefundStatus {
        REFUNDS(-1, "退费数据"),
        APPLY_FOR_A_REFUND(-2, "申请退药"),
        REFUSAL_TO_RETURN_THE_DRUG(-3, "拒绝退药");

        private final int code;
        private final String name;

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

    }

    public HospitalizationCostsService(HospitalizationCostsDao dao, XiangMuLuRuDao luRuDao, PublicServer publicServer, UserCache userCache) {
        this.dao = dao;
        this.luRuDao = luRuDao;
        this.publicServer = publicServer;
        this.userCache = userCache;
    }

    /**
     * 项目录入退费
     *
     * @param param 参数
     * @return 提示
     */
    @Transactional(rollbackFor = Exception.class)
    public ResultVo<String> refund(ZyDetailRefundParam param) {
        log.info("退费数组: {}", JSON.toJSONString(param));
        UserInfo userInfo = userCache.getUserInfoByToken();
        if (userInfo == null) {
            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "退费人员信息不存在，请重新登录。");
        }
        // 婴儿标志
        int infantFlag = PublicServer.getInfantFlag(param.getInpatientNo());
        if (infantFlag == 1) {
            param.setInpatientNo(param.getInpatientNo().split("\\$")[0]);
        }

        preCheck(param, userInfo);

        // 获取 患者的账页号
        Integer ledgerSn = publicServer.getLedgerSn(param.getInpatientNo(), param.getAdmissTimes());
        // 查询原来的数据
        QueryWrapper<ZyDetailChargeTable> qw = new QueryWrapper<>();
        qw.eq("inpatient_no", param.getInpatientNo())
                .eq("admiss_times", param.getAdmissTimes())
                .eq("ledger_sn", ledgerSn)
                .in("detail_sn", param.getDetailSn());

        List<ZyDetailChargeTable> tables = dao.selectList(qw);

        // 获取患者自己的科室和下面的子科室
        Set<String> deptList;

        if (userInfo.getDeptCode().startsWith("8")) {
            deptList = luRuDao.chaXunZhiZXinKeShi(userInfo.getDeptCode());
            deptList.add(userInfo.getDeptCode());
        } else {
            deptList = publicServer.getChildDeptByUserCode();
        }
        // 需要生成退药单
        List<ZyDetailChargeTable> generateAReturnOrder = new ArrayList<>();
        // 只退费
        List<ZyDetailChargeTable> refundOnly = new ArrayList<>();
        // 医技录入的药品退费
        List<ZyDetailChargeTable> medicalTechnologyDrugReturnForm = new ArrayList<>();
        // 是否是毒麻药品
        CacheOnce<Integer> drugKind = new CacheOnce<>();
        // 药品的详细信息
        CacheOnce<OrderInfo> orderCacheOnce = new CacheOnce<>();

        // 患者最大的 detail_sn
        int maxDetailSn = publicServer.getMaxDetailSn(param.getInpatientNo(), param.getAdmissTimes());

        StringBuilder error = new StringBuilder();

        for (ZyDetailChargeTable item : tables) {
            maxDetailSn += 1;

            StringBuilder sb = new StringBuilder();
            if (StringUtil.isBlank(item.getSerial())) {
                item.setSerial("00");
            }
            // -3 退药申请，拒绝退药申请是可以重新申请的
            if (item.getOriDetailSn() != null && item.getOriDetailSn().equals(RefundStatus.REFUSAL_TO_RETURN_THE_DRUG.getCode())) {
                item.setOriDetailSn(null);
            }
            if (item.getOriDetailSn() != null && item.getOriDetailSn().equals(RefundStatus.APPLY_FOR_A_REFUND.code)) {
                sb.append("该药品在申请退药。");
            }
            if (item.getOriDetailSn() != null) {
                sb.append("该数据为退费数据。");
            }
            if (BigUtils.bigXiaoYu(item.getChargeFee(), BigDecimal.ZERO) || BigUtils.bigXiaoYu(item.getChargeAmount(), BigDecimal.ZERO)) {
                sb.append("该数据为负数。");
            }
            if (StringUtil.isBlank(item.getExecUnit())) {
                item.setExecUnit(userInfo.getDeptCode());
            }
            if ("BILL02".equals(item.getChargeCode())) {
                sb.append("中药无法退费。");
            }
            String orderNo = item.getOrderNo() == null ?
                    "" : item.getOrderNo().stripTrailingZeros().toPlainString();
            // 项目判断
            if ("00".equals(item.getSerial())) {
                if (judgmentOfTheDepartment(userInfo.getCode(), item, deptList)) {
                    sb.append("请对应的执行科室或申请科室退费。");
                }
                // 只退费设置
                refundOnly.add(setDefaultValuesAndAutoincrements(item, maxDetailSn, RefundStatus.REFUNDS.code));
            } else {
                // 药品判断
                // 医技科室录入的费用
                if ("3".equals(orderNo)) {
                    if (!deptList.contains(item.getExecUnit())) {
                        sb.append("此药品为医技科室录入，请对应的医技科室退费。");
                    }
                    refundOnly.add(setDefaultValuesAndAutoincrements(item, maxDetailSn, RefundStatus.REFUNDS.code));
                    medicalTechnologyDrugReturnForm.add(setDefaultValuesAndAutoincrements(item, maxDetailSn, RefundStatus.REFUNDS.code));
                } else {
                    // 仅退费不退药品
                    if ("refundOnly".equals(param.getRefundFlag())) {
                        refundOnly.add(setDefaultValuesAndAutoincrements(item, maxDetailSn, RefundStatus.REFUNDS.code));
                    } else {
                        // 退费又退药.不需要向refundOnly中添加数据，且oriDetailSn 要设置为 -2 代表申请退费
                        generateAReturnOrder.add(setDefaultValuesAndAutoincrements(item, maxDetailSn, RefundStatus.APPLY_FOR_A_REFUND.code));
                    }

                    OrderInfo order = orderCacheOnce.get(orderNo, (key) -> dao.selectDrugClassByOrderNo(new BigDecimal(orderNo)));

                    if (order == null) {
                        if (!"refundOnly".equals(param.getRefundFlag())) {
                            sb.append("没有查询到原药品对应的医嘱");
                        }
                    } else {
                        if (!"refundOnly".equals(param.getRefundFlag())) {
//                            if ("1".equals(order.getClassCode())) {
//                                if (order.getRefundableFees().equals(0)) {
//                                    sb.append("医嘱给药方式为口服药，口服无法退费，药剂科要求只有包装规格为，瓶、盒、听、支可以退费又退药。");
//                                }
//                            }
                            if ("d".equals(order.getDrugClass())) {
                                order.setDrugClass("j");
                            }
                            // 医嘱表中会有 z 所以把所有 z 的换成 j
                            item.setDrugClass("z".equals(order.getDrugClass()) ? "j" : order.getDrugClass());
                        }
                    }
                }

                // 医技科室录入不要判断
                if (!"3".equals(orderNo)) {
                    Integer count = drugKind.get(item.getChargeCodeMx().trim() + "_" + item.getSerial().trim(), (key) -> dao.getDrugKing(item.getChargeCodeMx().trim(), item.getSerial().trim()));
                    if (count > 0 && "refundOnly".equals(param.getRefundFlag())) {
                        sb.append("药剂科要求，毒麻药药品无法，仅退费不退药品，请选择退费且退药。");
                    }
                }
            }


            if (StringUtil.notBlank(sb.toString())) {
                error.append("流水号：【").append(item.getDetailSn()).append("】").append(sb).append("<br />");
            }
        }
        // 循环结束
        notBlank(error.toString(), ExceptionEnum.LOGICAL_ERROR, error.toString());

        ZyActpatient patientInfo = publicServer.huoQuHuanZheXinXi(param.getInpatientNo());
        // 需要生成退药单
        if (ListUtil.notBlank(generateAReturnOrder)) {
            // 插入退费数据
            dao.detailsOfDrugReturnForm(generateAReturnOrder,
                    patientInfo.getInpatientNo(),
                    patientInfo.getAdmissTimes(),
                    patientInfo.getName(),
                    patientInfo.getBedNo(),
                    userInfo.getDeptCode(),
                    infantFlag,
                    param.getGroupNo(),
                    TokenUtil.getInstance().getTokenUserId(),
                    ledgerSn);
        }
        // 直接退费
        if (ListUtil.notBlank(refundOnly)) {
            // 插入退费数据
            dao.insertRefundData(refundOnly);
        }
        // 医技科室退费数据
        if (ListUtil.notBlank(medicalTechnologyDrugReturnForm)) {
            dao.shenQingYaoPing(patientInfo, medicalTechnologyDrugReturnForm, infantFlag, ledgerSn, userInfo.getCode());
        }
        dao.updateOriFlag(tables);
        return ResultVoUtil.success();
    }


    /**
     * 科室退费限制
     *
     * @param userCode 当前人员
     * @param item     数据
     * @param deptList 科室
     * @return 提示
     */
    private boolean judgmentOfTheDepartment(String userCode, ZyDetailChargeTable item, Set<String> deptList) {
        if (publicServer.noNeedRule()) {
            return false;
        }
        if (item.getOpIdCode().equals(userCode)) {
            return false;
        }
        return !deptList.contains(item.getExecUnit()) && !deptList.contains(item.getDeptCode());
    }

    /**
     * 退费前置校验
     *
     * @param param    参数
     * @param userInfo 用户信息
     */
    private void preCheck(ZyDetailRefundParam param, UserInfo userInfo) {
        isTrue(userInfo.getRoles().contains(36) || userInfo.getRoles().contains(1), "您没有权限退费。");
        isTrue(ListUtil.notBlank(param.getList()), "请先选择需要退费的数据，一次性退费不得超过100条。");
        isTrue(param.getList().size() <= 100, "项目退费一次性大于100条数据。");
        isTrue(StringUtil.notBlank(param.getInpatientNo()) && param.getAdmissTimes() != null, "患者信息不全");
        isTrue(luRuDao.getHuanZheSFZaiYuan(param.getInpatientNo(), param.getAdmissTimes()) > 0, "该患者已出院");
        Integer settlementFlag = luRuDao.getHuanZheSFJieSuan(param.getInpatientNo(), param.getAdmissTimes());
        if (settlementFlag == null || settlementFlag != 0) {
            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "该患者已经结算了");
        }
    }

    /**
     * 设置默认值
     *
     * @param item
     * @param maxDetailSn
     * @param oriDetailSn
     * @return
     */
    public ZyDetailChargeTable setDefaultValuesAndAutoincrements(ZyDetailChargeTable item, int maxDetailSn, int oriDetailSn) {
        String userCode = TokenUtil.getInstance().getTokenUserId();
        // 克隆一下 item 的数据
        ZyDetailChargeTable clone = EntityCopy.Copy(item, ZyDetailChargeTable.class);
        // 取负数的费用
        clone.setChargeFee(item.getChargeFee().negate());
        clone.setChargeAmount(item.getChargeAmount().negate());
        // 执行人
        clone.setOpIdCode(userCode);
        clone.setConfirmId(userCode);
        // 设置原来的流水号
        clone.setOriDetailSn(item.getDetailSn());
        Date now = new Date();
        clone.setGenTime(now);
        clone.setChargeDate(item.getChargeDate());
        // 设置新的流水号
        clone.setDetailSn(maxDetailSn);
        // 这个是唯一要改变的东西
        item.setOriDetailSn(oriDetailSn);

        return clone;
    }

    public int refundsByQueryWrapper(QueryWrapper<ZyDetailChargeTable> queryWrapper, String patNo, Integer times) {
        List<ZyDetailChargeTable> list = dao.selectList(queryWrapper);
        if (ListUtil.isBlank(list)) {
            return 0;
        }

        int maxDetailSn = publicServer.getMaxDetailSn(patNo, times);
        List<ZyDetailChargeTable> refundOnly = new ArrayList<>();

        for (ZyDetailChargeTable item : list) {
            maxDetailSn += 1;
            refundOnly.add(setDefaultValuesAndAutoincrements(item, maxDetailSn, RefundStatus.REFUNDS.code));
        }

        // 直接退费
        if (ListUtil.notBlank(refundOnly)) {
            // 插入退费数据
            dao.insertRefundData(refundOnly);
        }

        dao.updateOriFlag(list);
        return list.size();
    }

    @Transactional(rollbackFor = Exception.class)
    public int thereAreNoRestrictionsOnRefunds(RefundParam param) {
        QueryWrapper<ZyDetailChargeTable> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("inpatient_no", param.getPatNo())
                .eq("admiss_times", param.getTimes())
                .in("cast(ledger_sn as varchar(500)) + '_' + cast(detail_sn as varchar(500))", param.getKey())
                .gt("charge_fee", 0)
                .isNull("ori_detail_sn");

        return refundsByQueryWrapper(queryWrapper, param.getPatNo(), param.getTimes());
    }


    @Transactional(rollbackFor = Exception.class)
    public void refundsByPatientOrder(String patNo, Integer times, BigDecimal orderNo) {
        QueryWrapper<ZyDetailChargeTable> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("inpatient_no", patNo)
                .eq("admiss_times", times)
                .eq("ABS(order_no)", orderNo)
                .gt("charge_fee", 0)
                .isNull("ori_detail_sn");
        refundsByQueryWrapper(queryWrapper, patNo, times);
    }


}
