package com.ywt.outpatient.rpc.provider;


import com.ywt.biz.common.constant.YwtCommonRespCode;
import com.ywt.biz.common.exception.YwtCommonException;
import com.ywt.biz.common.util.Checker;
import com.ywt.biz.common.util.DateUtil;
import com.ywt.biz.common.util.serializers.JsonSerializer;
import com.ywt.gapi.ResultCode;
import com.ywt.gapi.taihe.biz.*;
import com.ywt.outpatient.domain.entity.center.*;
import com.ywt.outpatient.service.rpc.common.ConfigProvider;
import com.ywt.outpatient.service.rpc.common.TaiheWeChatMsgService;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.time.LocalDate;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author Walker
 * Created on 2020/7/13
 */
@DubboService
public class MriProvider extends DubboTaiheBizServiceTriple.TaiheBizServiceImplBase {
    private final Logger logger = LoggerFactory.getLogger(MriProvider.class);

    @Autowired
    private MriSubscribeRepository mriSubscribeRepository;

    @Autowired
    private MriScreeningRepository mriScreeningRepository;

    @Autowired
    private MriScreeningQuestionOptionsRepository mriScreeningQuestionOptionsRepository;

    @Autowired
    private ConfigProvider configProvider;

    @Autowired
    private TaiheWeChatMsgService taiheWeChatMsgService;

    public static final int LEVEL_0 = 0;
    public static final int LEVEL_1 = 1;
    public static final int LEVEL_2 = 2;
    public static final String BIRTHDAY_FORMAT = "yyyy-MM-dd";
    public static final String REPLACEMENT_INPUT = "${input}";

    @Override
    public MriBaseResponse mriBooking(MriBookingRequest request) {
        int userId = request.getUserId();
        String name = request.getName();
        int sex = request.getSex();
        String mobile = request.getMobile();
        String date = request.getDate();
        MriBaseResponse.Builder builder = MriBaseResponse.newBuilder();
        try {
            Date bookDate = validateBookDate(date);
            if (userId <= 0) throw new YwtCommonException(YwtCommonRespCode.P_ERR, "用户 id 不正确");
            // 一名患者，一个日期，只能预约一个号，以“手机号”来判断是一个患者。当患者重复预约时，点击提交，提示“患者已预约，请勿重复预约”
            if (checkHasBooked(mobile, bookDate)) throw new YwtCommonException(YwtCommonRespCode.P_ERR, "患者已预约，请勿重复预约");
            // 当患者被后台标志 N 次以上（包含 N 次）预约不就诊的，拉入黑名单，患者点击提交时，提示“用户过去 N 次预约未就诊，已被拉入黑名单”。
            if (checkIfInBlackList(mobile)) throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("用户过去 %d 次预约未就诊，已被拉入黑名单",
                    configProvider.getMriBlackListLimitation()));
            // 每天最多只有 N 个号源
            // TODO: 并发控制
            if (checkIfFullyBooked(bookDate)) throw new YwtCommonException(YwtCommonRespCode.P_ERR, "当前选择的时间已约满，请更换时间重试");
            // 可以预约，插入数据
            MriSubscribe subscribe = new MriSubscribe();
            subscribe.setCreateTime(new Date());
            subscribe.setUpdateTime(new Date());
            subscribe.setDeleted(0);
            subscribe.setPatientMobile(mobile);
            subscribe.setPatientName(name);
            subscribe.setSex(sex);
            subscribe.setUserId(userId);
            subscribe.setSubscribeTime(bookDate);
            subscribe.setStatus(0);
            mriSubscribeRepository.save(subscribe);
            // 预约成功的患者，对应的号源减1，并推送太和分院公众号微信模板消息提醒
            taiheWeChatMsgService.sendMsgForMriBooking(userId, name, mobile, date);
            builder.setCode(ResultCode.SUCCEED_VALUE).setInfo("操作成功");
            return builder.build();
        } catch (YwtCommonException e) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setInfo(e.getMessage());
            return builder.build();
        } catch (Exception e) {
            logger.error("MriService#mriBooking(request={}):\n {}", request, e.getMessage(), e);
            return builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setInfo(e.getMessage()).build();
        }
    }

    private Date validateBookDate(String date) throws YwtCommonException {
        Date bookDate = DateUtil.stringToDate(date, BIRTHDAY_FORMAT);
        if (bookDate == null) throw new YwtCommonException(YwtCommonRespCode.P_ERR, "日期格式不正确");
        // 只能预约未来 N 天的号（当天不能预约）
        LocalDate bookLocalDate = DateUtil.convertToLocalDate(bookDate);
        LocalDate today = LocalDate.now();
        LocalDate maxDate = today.plusDays(configProvider.getMriMaxBookDay());
        if (!bookLocalDate.isAfter(today) || bookLocalDate.isAfter(maxDate))
            throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("只能预约未来 %d 天的号", configProvider.getMriMaxBookDay()));
        return bookDate;
    }

    private boolean checkHasBooked(String mobile, Date date) {
        return mriSubscribeRepository.findFirstByPatientMobileAndSubscribeTime(mobile, date) != null;
    }

    private boolean checkIfInBlackList(String mobile) {
        return mriSubscribeRepository.findByPatientMobileForBreakPromise(mobile).size() >= configProvider.getMriBlackListLimitation();
    }

    private boolean checkIfFullyBooked(Date date) {
        return mriSubscribeRepository.findBySubscribeTime(date).size() >= configProvider.getMriMaxBookNum();
    }

    @Override
    public GetScreeningTableResponse getScreeningTable(GetScreeningTableRequest request) {
        int userId = request.getUserId();
        GetScreeningTableResponse.Builder builder = GetScreeningTableResponse.newBuilder();
        try {
            ScreeningTable table = getScreeningTableByUserId(userId);
            builder.setCode(ResultCode.SUCCEED_VALUE)
                    .setMsg("操作成功")
                    .setScreeningTable(table);
            return builder.build();
        } catch (Exception e) {
            logger.error("MriService#getScreeningTable(request={}):\n {}", request, e.getMessage(), e);
            return builder.setCode(ResultCode.APP_ERROR_VALUE)
                    .setMsg(e.getMessage())
                    .build();
        }
    }

    private String getCheckedSummaryByCategory(List<Question> list, String category) {
        List<Question> filteredQuestions = list.stream()
                .filter(question -> category.equals(question.getCategory()))
                .collect(Collectors.toList());
        StringBuilder sb = new StringBuilder();
        for (Question filteredQuestion : filteredQuestions) {
            List<QuestionOption> checkedOptions = filteredQuestion.getOptionsList()
                    .stream()
                    .filter(QuestionOption::getValue1)
                    .collect(Collectors.toList());
            if (!checkedOptions.isEmpty()) {
                sb.append(filteredQuestion.getQuestionId())
                        .append("、")
                        .append(getCheckedString(checkedOptions))
                        .append("\n");
            }
        }
        return sb.toString();
    }

    private String getCheckedString(List<QuestionOption> list) {
        StringBuilder sb = new StringBuilder();
        for (QuestionOption option : list) {
            if (option.getValue1()) {
                String optName = option.getName();
                optName = optName.replaceFirst(Pattern.quote(REPLACEMENT_INPUT), Checker.getStringValue(option.getValue2()));
                optName = optName.replaceFirst(Pattern.quote(REPLACEMENT_INPUT), Checker.getStringValue(option.getValue3()));
                sb.append(optName).append(" ");
                List<QuestionOption> childrenList = option.getChildrenList();
                if (childrenList != null && !childrenList.isEmpty()) {
                    String childrenString = getCheckedString(childrenList);
                    sb.append(childrenString).append(" ");
                }
            }
        }
        return sb.toString();
    }

    @Override
    public SubmitScreeningTableResponse submitScreeningTable(SubmitScreeningTableRequest request) {
        int userId = request.getUserId();
        ScreeningTable table = request.getTable();
        String submitContent = request.getSubmitContent();
        PatientInfo patientInfo = table.getPatientInfo();
        SubmitScreeningTableResponse.Builder builder = SubmitScreeningTableResponse.newBuilder();
        try {
            int tableId = table.getTableId();
            MriScreening mriScreening;
            if (tableId > 0) {
                // 更新筛查表
                mriScreening = mriScreeningRepository.findById(tableId).orElse(null);
                if (mriScreening == null) throw new YwtCommonException(YwtCommonRespCode.P_ERR, String.format("记录 id 不存在：%d", tableId));
            } else {
                // 插入新的记录
                mriScreening = new MriScreening();
                mriScreening.setDeleted(0);
                mriScreening.setCreateTime(new Date());
            }
            if (patientInfo != null) {
                mriScreening.setPatientName(patientInfo.getPatientName());
                mriScreening.setSex(patientInfo.getSex());
                Date d = DateUtil.stringToDate(patientInfo.getBirthday(), BIRTHDAY_FORMAT);
                if (d == null) throw new YwtCommonException(YwtCommonRespCode.P_ERR, "出生年月格式不正确！");
                mriScreening.setBirthday(d);
                mriScreening.setPatientMobile(patientInfo.getPatientMobile());
                mriScreening.setAddress(patientInfo.getAddress());
            }
            List<Question> questions = table.getQuestionsList();
            String baseDiseaseHistoryMg = getCheckedSummaryByCategory(questions, "基础病史");
            mriScreening.setBaseDiseaseHistoryMg(baseDiseaseHistoryMg);
            String geneticDiseaseHistoryMg = getCheckedSummaryByCategory(questions, "遗传病史");
            mriScreening.setGeneticDiseaseHistoryMg(geneticDiseaseHistoryMg);
            String livingHabitsMg = getCheckedSummaryByCategory(questions, "生活习惯");
            mriScreening.setLivingHabitsMg(livingHabitsMg);
            String clinicalSymptomsMg = getCheckedSummaryByCategory(questions, "临床症状");
            mriScreening.setClinicalSymptomsMg(clinicalSymptomsMg);
            String rmiResult = getCheckedSummaryByCategory(questions, "磁共振检查结果");
            mriScreening.setRmiResult(rmiResult);

            mriScreening.setUpdateTime(new Date());
            mriScreening.setUserId(userId);
            mriScreening.setSubmitContent(submitContent);
            mriScreening.setCompleted(request.getCompleted());
            MriScreening result = mriScreeningRepository.save(mriScreening);
            builder.setCode(ResultCode.SUCCEED_VALUE).setMsg("操作成功").setTableId(result.getId());
            return builder.build();
        } catch (YwtCommonException e) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setMsg(e.getMessage());
            return builder.build();
        } catch (Exception e) {
            logger.error("MriService#submitScreeningTable(request={}):\n {}", request, e.getMessage(), e);
            return builder.setCode(ResultCode.APP_ERROR_VALUE)
                    .setMsg(e.getMessage())
                    .build();
        }
    }

    /**
     * 返回所有问题选项，空值
     *
     * @return question list
     */
    private List<Question> getAllQuestions() {
        List<MriScreeningQuestionOptions> optionList = mriScreeningQuestionOptionsRepository.findAllByDeletedOrderByWeightAsc(0);
        Map<Integer, List<MriScreeningQuestionOptions>> questionsMap = optionList.stream()
                .collect(Collectors.groupingBy(MriScreeningQuestionOptions::getQuestionId));
        List<Question> questions = new LinkedList<>();
        for (Map.Entry<Integer, List<MriScreeningQuestionOptions>> qIdListEntry : questionsMap.entrySet()) {
            int questionId = qIdListEntry.getKey();
            List<MriScreeningQuestionOptions> options = qIdListEntry.getValue();
            String category = options.isEmpty() ? "" : options.get(0).getCategoryName();
            List<com.ywt.outpatient.domain.models.QuestionOption> questionOptions = getNestedOptionList(options);
            Question question = Question.newBuilder()
                    .setQuestionId(questionId)
                    .setCategory(category)
                    .addAllOptions(convertModel2GrpcDefObj(questionOptions))
                    .build();
            questions.add(question);
        }
        return questions;
    }

    private List<QuestionOption> convertModel2GrpcDefObj(List<com.ywt.outpatient.domain.models.QuestionOption> list) {
        return list.stream()
                .map(questionOption -> {
                    List<QuestionOption> options = new LinkedList<>();
                    List<com.ywt.outpatient.domain.models.QuestionOption> children = questionOption.getChildren();
                    if (children != null && !children.isEmpty()) {
                        options = convertModel2GrpcDefObj(children);
                    }
                    return QuestionOption.newBuilder()
                            .setId(Checker.getIntegerValue(questionOption.getId()))
                            .setName(Checker.getStringValue(questionOption.getName()))
                            .setValue1(Checker.getBooleanValue(questionOption.getValue1()))
                            .setValue2(Checker.getStringValue(questionOption.getValue2()))
                            .setValue3(Checker.getStringValue(questionOption.getValue3()))
                            .addAllChildren(options)
                            .build();
                })
                .collect(Collectors.toList());
    }

    private List<com.ywt.outpatient.domain.models.QuestionOption> getNestedOptionList(List<MriScreeningQuestionOptions> list) {
        List<com.ywt.outpatient.domain.models.QuestionOption> lv0 = getQuestionOptionsByLevel(list, LEVEL_0);
        List<com.ywt.outpatient.domain.models.QuestionOption> lv1 = getQuestionOptionsByLevel(list, LEVEL_1);
        List<com.ywt.outpatient.domain.models.QuestionOption> lv2 = getQuestionOptionsByLevel(list, LEVEL_2);
        List<com.ywt.outpatient.domain.models.QuestionOption> lst1 = mergeChildrenOptions(lv1, lv2);
        return mergeChildrenOptions(lv0, lst1);
    }

    private List<com.ywt.outpatient.domain.models.QuestionOption> mergeChildrenOptions(List<com.ywt.outpatient.domain.models.QuestionOption> parentList, List<com.ywt.outpatient.domain.models.QuestionOption> childrenList) {
        if (!childrenList.isEmpty()) {
            for (com.ywt.outpatient.domain.models.QuestionOption opParent : parentList) {
                List<com.ywt.outpatient.domain.models.QuestionOption> children = new LinkedList<>();
                for (com.ywt.outpatient.domain.models.QuestionOption opChild : childrenList) {
                    if (opParent.getId() == opChild.getParentId()) {
                        children.add(opChild);
                    }
                }
                opParent.setChildren(children);
            }
        }
        return parentList;
    }

    private List<com.ywt.outpatient.domain.models.QuestionOption> getQuestionOptionsByLevel(List<MriScreeningQuestionOptions> list, int level) {
        return list.stream()
                .filter(mriScreeningQuestionOptions -> mriScreeningQuestionOptions.getLevel() == level)
                .map(mriScreeningQuestionOptions -> {
                    com.ywt.outpatient.domain.models.QuestionOption qo = new com.ywt.outpatient.domain.models.QuestionOption();
                    qo.setId(mriScreeningQuestionOptions.getId());
                    qo.setName(mriScreeningQuestionOptions.getName());
                    qo.setParentId(mriScreeningQuestionOptions.getParentId());
                    return qo;
                })
                .collect(Collectors.toList());
    }

    /**
     * 根据用户 id 获取筛查表内容返回
     *
     * @param userId 用户 id
     * @return table
     */
    private ScreeningTable getScreeningTableByUserId(int userId) {
        // 获取当前用户最后一次提交的答案
        MriScreening mriScreening = mriScreeningRepository.findFirstByUserIdOrderByUpdateTimeDesc(userId);
        List<Question> questions = new LinkedList<>();
        PatientInfo.Builder patientInfoBuilder = PatientInfo.newBuilder();
        int tableId = 0;
        if (mriScreening == null) {
            // 没有提交过答案，返回空白问题列表
            questions = getAllQuestions();
        } else {
            // 已提交答案，检查是否完成所有问题
            boolean completed = Checker.getBooleanValue(mriScreening.getCompleted());
            if (completed) {
                // 返回新的问题列表
                questions = getAllQuestions();
            } else {
                // 返回上次提交的内容
                String submitContent = mriScreening.getSubmitContent();
                com.ywt.outpatient.domain.models.ScreeningTable table = JsonSerializer.from(submitContent, com.ywt.outpatient.domain.models.ScreeningTable.class);
                if (table != null && table.getQuestions() != null) {
                    questions = table.getQuestions().stream()
                            .map(question -> Question.newBuilder()
                                    .setQuestionId(question.getQuestionId())
                                    .setCategory(question.getCategory())
                                    .addAllOptions(convertModel2GrpcDefObj(question.getOptions()))
                                    .build())
                            .collect(Collectors.toList());
                }
                patientInfoBuilder.setPatientName(mriScreening.getPatientName());
                patientInfoBuilder.setSex(mriScreening.getSex());
                patientInfoBuilder.setBirthday(DateUtil.convertToString(mriScreening.getBirthday(), BIRTHDAY_FORMAT));
                patientInfoBuilder.setPatientMobile(mriScreening.getPatientMobile());
                patientInfoBuilder.setAddress(mriScreening.getAddress());
                tableId = mriScreening.getId();
            }
        }
        return ScreeningTable.newBuilder()
                .setPatientInfo(patientInfoBuilder.build())
                .addAllQuestions(questions)
                .setTableId(tableId)
                .build();
    }

    @Override
    public GetAvailableBookNumResponse getAvailableBookNum(GetAvailableBookNumRequest request) {
        String date = request.getDate();
        GetAvailableBookNumResponse.Builder builder = GetAvailableBookNumResponse.newBuilder();
        try {
            Date bookDate = validateBookDate(date);
            int leftBookNum = configProvider.getMriMaxBookNum() - mriSubscribeRepository.findBySubscribeTime(bookDate).size();
            if (leftBookNum < 0) leftBookNum = 0;
            builder.setCode(ResultCode.SUCCEED_VALUE).setMsg("OK").setCount(leftBookNum);
            return builder.build();
        } catch (YwtCommonException e) {
            builder.setCode(ResultCode.PARAMETER_ERROR_VALUE).setMsg(e.getMessage());
            return builder.build();
        } catch (Exception e) {
            logger.error("MriService#getAvailableBookNum(request={}):\n {}", request, e.getMessage(), e);
            return builder.setCode(ResultCode.APP_ERROR_VALUE)
                    .setMsg(e.getMessage())
                    .build();
        }
    }
}
