package com.example.hospital.patient.wx.api.service.impl;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateRange;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import com.example.hospital.patient.wx.api.common.PageUtils;
import com.example.hospital.patient.wx.api.db.dao.*;
import com.example.hospital.patient.wx.api.db.pojo.MedicalRegistrationEntity;
import com.example.hospital.patient.wx.api.service.FaceAuthService;
import com.example.hospital.patient.wx.api.service.PaymentService;
import com.example.hospital.patient.wx.api.service.RegistrationService;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

@Service
public class RegistrationServiceImpl implements RegistrationService {
    @Resource
    private DoctorWorkPlanDao doctorWorkPlanDao;

    @Resource
    private MedicalRegistrationDao medicalRegistrationDao;

    @Resource
    private DoctorWorkPlanScheduleDao doctorWorkPlanScheduleDao;

    @Resource
    private UserInfoCardDao userInfoCardDao;

    @Resource
    private FaceAuthService faceAuthService;

    @Resource
    private UserDao userDao;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private PaymentService paymentService;

    private String notifyUrl = "/registration/transactionCallback";

    @Override
    public ArrayList<String> searchCanRegisterInDateRange(Map param) {
        ArrayList<String> list = doctorWorkPlanDao.searchCanRegisterInDateRange(param);
        DateTime startDate = DateUtil.parse(MapUtil.getStr(param, "startDate"));
        DateTime endDate = DateUtil.parse(MapUtil.getStr(param, "endDate"));
        DateRange range = DateUtil.range(startDate, endDate, DateField.DAY_OF_MONTH);
        ArrayList result = new ArrayList();
        while (range.hasNext()) {
            String date = range.next().toDateStr();
            if (list.contains(date)) {
                result.add(new HashMap<>() {{
                    put("date", date);
                    put("status", "出诊");
                }});
            } else {
                result.add(new HashMap<>() {{
                    put("date", date);
                    put("status", "无号");
                }});
            }
        }
        return result;
    }

    @Override
    public ArrayList<HashMap> searchDeptSubDoctorPlanInDay(Map param) {
        ArrayList<HashMap> list = doctorWorkPlanDao.searchDeptSubDoctorPlanInDay(param);
        return list;
    }

    @Override
    public String checkRegisterCondition(Map param) {
        param.put("today", DateUtil.today());
        long count = medicalRegistrationDao.searchRegistrationCountInToday(param);
        if (count == 3) {
            return "已经达到当天挂号上限";
        }
        Integer id = medicalRegistrationDao.hasRegisterRecordInDay(param);
        if (id != null) {
            return "已经挂过该诊室的号";
        }
        int userId = MapUtil.getInt(param, "userId");
        Boolean bool = userInfoCardDao.searchExistFaceModel(userId);
        if (bool == null || !bool) {
            return "不存在面部模型";
        }
        bool = faceAuthService.hasFaceAuthInDay(param);
        if (!bool) {
            return "当日没有人脸验证记录";
        }
        return "满足挂号条件";
    }

    @Override
    public ArrayList<HashMap> searchDoctorWorkPlanSchedule(Map param) {
        ArrayList<HashMap> list = doctorWorkPlanScheduleDao.searchDoctorWorkPlanSchedule(param);
        return list;
    }

    @Override
    public HashMap registerMedicalAppointment(Map param) {
        int workPlanId = MapUtil.getInt(param, "workPlanId");
        int scheduleId = MapUtil.getInt(param, "scheduleId");
        String key = "doctor_schedule_" + scheduleId;
        if (!redisTemplate.hasKey(key)) {
            return null;
        }
        Object execute = redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                operations.watch(key);
                Map entry = operations.opsForHash().entries(key);
                int maximum = Integer.parseInt(entry.get("maximum").toString());
                int num = Integer.parseInt(entry.get("num").toString());
                if (num < maximum) {
                    operations.multi();
                    operations.opsForHash().increment(key, "num", 1);
                    return operations.exec();
                } else {
                    operations.unwatch();
                    return null;
                }
            }
        });
        if(execute==null){
            return null;
        }
        try{
            int userId = MapUtil.getInt(param, "userId");
            HashMap map = userDao.searchOpenId(userId);
            String openId = MapUtil.getStr(map, "openId");
            int patientCardId = MapUtil.getInt(map, "patientCardId");
            int doctorId = MapUtil.getInt(param, "doctorId");
            int deptSubId = MapUtil.getInt(param, "deptSubId");
            String date = MapUtil.getStr(param, "date");
            int slot = MapUtil.getInt(param, "slot");
            String temp = MapUtil.getStr(param, "amount");
            int total = NumberUtil.mul(temp, "100").intValue();
            String outTradeNo = IdUtil.simpleUUID().toUpperCase();

            ObjectNode objectNode = paymentService.unifiedOrder(outTradeNo, openId, total, "挂号费", notifyUrl, null);
            String prepayId = objectNode.get("prepay_id").textValue();

            MedicalRegistrationEntity entity = new MedicalRegistrationEntity();
            entity.setWorkPlanId(workPlanId);
            entity.setDoctorScheduleId(scheduleId);
            entity.setPatientCardId(patientCardId);
            entity.setDoctorId(doctorId);
            entity.setDeptSubId(deptSubId);
            entity.setDate(date);
            entity.setSlot(slot);
            entity.setAmount(new BigDecimal(temp));
            entity.setOutTradeNo(outTradeNo);
            entity.setPrepayId(prepayId);
            medicalRegistrationDao.insert(entity);

            doctorWorkPlanDao.updateNumById(new HashMap(){{
                put("id", workPlanId);
                put("n", 1);
            }});

            doctorWorkPlanScheduleDao.updateNumById(new HashMap(){{
                put("id", scheduleId);
                put("n", 1);
            }});

            redisTemplate.opsForHash().putAll("registration_payment_"+outTradeNo,new HashMap(){{
                put("workPlanId", workPlanId);
                put("scheduleId", scheduleId);
                put("outTradeNo", outTradeNo);
            }});
            DateTime now=new DateTime();
            now.offset(DateField.MINUTE,15);
            redisTemplate.expireAt("registration_payment_"+outTradeNo,now);

            HashMap result=new HashMap(){{
                put("outTradeNo", outTradeNo);
                put("prepayId", prepayId);
                put("timeStamp", objectNode.get("timeStamp").asText());
                put("nonceStr", objectNode.get("nonceStr").asText());
                put("package", objectNode.get("package").asText());
                put("signType", objectNode.get("signType").asText());
                put("paySign", objectNode.get("paySign").asText());
            }};
            return result;
        }catch (Exception e) {
            if (redisTemplate.hasKey(key)) {
                //恢复缓存该日程已经挂号数量
                redisTemplate.opsForHash().increment(key, "num", -1);
            }
            throw e;
        }
    }

    @Override
    @Transactional
    public void updatePayment(Map param) {
        String outTradeNo = MapUtil.getStr(param, "outTradeNo");
        redisTemplate.delete("registration_payment_" + outTradeNo);
        medicalRegistrationDao.updatePayment(param);
    }

    @Override
    @Transactional
    public boolean searchPaymentResult(String outTradeNo) {
        String transactionId=paymentService.searchPaymentResult(outTradeNo);
        if(transactionId!=null){
            this.updatePayment(new HashMap(){{
                put("outTradeNo", outTradeNo);
                put("transactionId", transactionId);
                put("paymentStatus", 2);
            }});
            return true;
        }
        else{
            return false;
        }

    }

    @Override
    public PageUtils searchRegistrationByPage(Map param) {
        ArrayList list = null;
        long count = medicalRegistrationDao.searchRegistrationCount(param);
        if (count > 0) {
            list = medicalRegistrationDao.searchRegistrationByPage(param);
        } else {
            list = new ArrayList();
        }
        int page = MapUtil.getInt(param, "page");
        int length = MapUtil.getInt(param, "length");
        PageUtils pageUtils = new PageUtils(list, count, page, length);
        return pageUtils;
    }

    @Override
    public HashMap searchRegistrationInfo(Map param) {
        HashMap map = medicalRegistrationDao.searchRegistrationInfo(param);
        return map;
    }

}
