package com.openhis.web.doctorstation.utils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.springframework.stereotype.Component;

import com.core.common.utils.AssignSeqUtil;
import com.openhis.common.enums.AssignSeqEnum;
import com.openhis.web.doctorstation.dto.AdviceSaveDto;

/**
 * 处方号工具类
 */
@Component
public class PrescriptionUtils {

    @Resource
    private AssignSeqUtil assignSeqUtil;

    private static final Integer MAX_SINGLE_PRESCRIPTION_NUM = 5; // 单个处方药品种类上限
    private static final BigDecimal MAX_SINGLE_PRESCRIPTION_PRICE = new BigDecimal("500"); // 单个处方金额上限

    /**
     * 主入口方法：为药品请求生成处方号
     */
    public void generatePrescriptionNumbers(List<AdviceSaveDto> medicineList) {
        if (medicineList == null || medicineList.isEmpty()) {
            return;
        }
        // 1. 按诊断ID分组（不同诊断必须分开）
        Map<Long, List<AdviceSaveDto>> diagnosisGroups =
            medicineList.stream().collect(Collectors.groupingBy(AdviceSaveDto::getConditionDefinitionId));
        // 2. 处理每个诊断组
        diagnosisGroups.values().forEach(this::processDiagnosisGroup);
    }

    /**
     * 处理单个诊断组
     */
    private void processDiagnosisGroup(List<AdviceSaveDto> diagnosisGroup) {
        if (diagnosisGroup.isEmpty()) {
            return;
        }
        // 1. 按药品性质分组
        Map<String, List<AdviceSaveDto>> pharmacologyGroups =
            diagnosisGroup.stream().collect(Collectors.groupingBy(AdviceSaveDto::getPharmacologyCategoryCode));
        // 2. 处理每个药品性质组
        pharmacologyGroups.values().forEach(pharmaGroup -> {
            // 2.1 先处理有分组ID的药品（确保它们不会被拆分）
            Map<Long, List<AdviceSaveDto>> groupedMedicines = pharmaGroup.stream().filter(m -> m.getGroupId() != null)
                .collect(Collectors.groupingBy(AdviceSaveDto::getGroupId));
            // 2.2 无分组ID的药品单独处理
            List<AdviceSaveDto> ungroupedMedicines =
                pharmaGroup.stream().filter(m -> m.getGroupId() == null).collect(Collectors.toList());
            // 2.3 合并处理 - 每个分组ID的药品作为一个整体，与无分组ID的药品一起按限制拆分
            List<List<AdviceSaveDto>> allGroups = new ArrayList<>();
            // 添加有分组ID的药品（每个分组作为一个整体）
            groupedMedicines.values().forEach(allGroups::add);
            // 添加无分组ID的药品（按种类限制拆分）
            allGroups.addAll(splitByQuantityLimit(ungroupedMedicines));
            // 2.4 对合并后的所有组按金额限制进行最终拆分
            List<List<AdviceSaveDto>> finalGroups = new ArrayList<>();
            allGroups.forEach(group -> {
                finalGroups.addAll(splitByPriceLimit(group));
            });

            /**
             * 如下文字适用 2.5 前提条件: 当 groupedMedicines 和 ungroupedMedicines都不为空的情况下 可以得知 finalGroups
             * 的size至少是2(有组号的集合,无组号的集合) 假设有组号的集合为 groupList1 (也可能还有groupList2,groupList3 ...) 假设无组号的集合为 noGroupList1
             * (也可能还有noGroupList2,noGroupList3 ...) 注意:上述的groupList1和noGroupList1只是为了方便描述finalGroups的组成部分,实际并不存在这样的变量
             *
             * 需求:对finalGroups的数据结构进行重新整合(groupList1,groupList2,groupList3,noGroupList1,noGroupList2,noGroupList3
             * 这些集合集合不可以被拆分) 当finalGroups里的集合size < MAX_SINGLE_PRESCRIPTION_NUM
             * 时,groupList1,noGroupList1这些集合元素进行整合,保证每个重新组合的size依旧 <= MAX_SINGLE_PRESCRIPTION_NUM;
             */

            // 2.5 重新整合小的分组（保持原有分组不被拆分）
            List<List<AdviceSaveDto>> mergedGroups = mergeSmallGroups(finalGroups);
            // 2.6 分配处方号
            assignPrescriptionNumbers(mergedGroups, pharmaGroup.get(0).getPharmacologyCategoryCode());
        });
    }

    /**
     * 合并小的分组（保持原有分组不被拆分）
     */
    private List<List<AdviceSaveDto>> mergeSmallGroups(List<List<AdviceSaveDto>> groups) {
        if (groups.size() <= 1) {
            return groups;
        }
        List<List<AdviceSaveDto>> result = new ArrayList<>();
        List<AdviceSaveDto> currentMergedGroup = new ArrayList<>();
        for (List<AdviceSaveDto> group : groups) {
            // 检查当前分组是否可以合并到现有组中
            if (canMerge(currentMergedGroup, group)) {
                currentMergedGroup.addAll(group);
            } else {
                // 如果不能合并，先保存当前合并组（如果不为空）
                if (!currentMergedGroup.isEmpty()) {
                    result.add(new ArrayList<>(currentMergedGroup));
                }
                // 开始新的合并组
                currentMergedGroup = new ArrayList<>(group);
            }
        }
        // 添加最后一个合并组
        if (!currentMergedGroup.isEmpty()) {
            result.add(currentMergedGroup);
        }
        return result;
    }

    /**
     * 检查两个分组是否可以合并（合并后不超过最大限制）
     */
    private boolean canMerge(List<AdviceSaveDto> currentGroup, List<AdviceSaveDto> newGroup) {
        if (currentGroup.isEmpty()) {
            return true; // 当前组合为空，可以直接添加
        }
        // 检查合并后的药品数量是否超过限制
        int mergedSize = currentGroup.size() + newGroup.size();
        if (mergedSize > MAX_SINGLE_PRESCRIPTION_NUM) {
            return false;
        }
        // 检查合并后的总金额是否超过限制
        BigDecimal currentTotal = calculateTotalPrice(currentGroup);
        BigDecimal newGroupTotal = calculateTotalPrice(newGroup);
        BigDecimal mergedTotal = currentTotal.add(newGroupTotal);
        return mergedTotal.compareTo(MAX_SINGLE_PRESCRIPTION_PRICE) <= 0;
    }

    /**
     * 计算分组的总金额
     */
    private BigDecimal calculateTotalPrice(List<AdviceSaveDto> medicines) {
        return medicines.stream().map(medicine -> medicine.getUnitPrice().multiply(medicine.getQuantity()))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    /**
     * 按药品种类上限拆分（仅用于无分组ID药品）
     */
    private List<List<AdviceSaveDto>> splitByQuantityLimit(List<AdviceSaveDto> medicines) {
        List<List<AdviceSaveDto>> result = new ArrayList<>();
        List<AdviceSaveDto> currentGroup = new ArrayList<>(MAX_SINGLE_PRESCRIPTION_NUM);
        for (AdviceSaveDto medicine : medicines) {
            if (currentGroup.size() >= MAX_SINGLE_PRESCRIPTION_NUM) {
                result.add(currentGroup);
                currentGroup = new ArrayList<>(MAX_SINGLE_PRESCRIPTION_NUM);
            }
            currentGroup.add(medicine);
        }
        if (!currentGroup.isEmpty()) {
            result.add(currentGroup);
        }
        return result;
    }

    /**
     * 按处方金额上限拆分（通用方法，处理所有药品）
     */
    private List<List<AdviceSaveDto>> splitByPriceLimit(List<AdviceSaveDto> medicines) {
        List<List<AdviceSaveDto>> result = new ArrayList<>();
        List<AdviceSaveDto> currentGroup = new ArrayList<>();
        BigDecimal currentTotal = BigDecimal.ZERO;
        for (AdviceSaveDto medicine : medicines) {
            // 计算单个药品总金额
            BigDecimal medicinePrice = medicine.getUnitPrice().multiply(medicine.getQuantity());
            // 特殊处理：单药品金额超限
            if (medicinePrice.compareTo(MAX_SINGLE_PRESCRIPTION_PRICE) > 0) {
                // 先保存当前组（如果有药品）
                if (!currentGroup.isEmpty()) {
                    result.add(currentGroup);
                    currentGroup = new ArrayList<>();
                    currentTotal = BigDecimal.ZERO;
                }
                // 该药品单独成方
                result.add(Collections.singletonList(medicine));
                continue;
            }
            // 检查添加后是否超限
            if (currentTotal.add(medicinePrice).compareTo(MAX_SINGLE_PRESCRIPTION_PRICE) > 0) {
                result.add(currentGroup);
                currentGroup = new ArrayList<>();
                currentTotal = BigDecimal.ZERO;
            }
            currentGroup.add(medicine);
            currentTotal = currentTotal.add(medicinePrice);
        }
        if (!currentGroup.isEmpty()) {
            result.add(currentGroup);
        }
        return result;
    }

    /**
     * 为最终分组分配处方号
     */
    private void assignPrescriptionNumbers(List<List<AdviceSaveDto>> groups, String pharmacologyCode) {
        groups.forEach(group -> {
            String prescriptionNo = generatePrescriptionNo(pharmacologyCode);
            group.forEach(medicine -> medicine.setPrescriptionNo(prescriptionNo));
        });
    }

    /**
     * 根据药品性质生成处方号
     */
    private String generatePrescriptionNo(String pharmacologyCategoryCode) {
        switch (pharmacologyCategoryCode) {
            case "2": // 麻醉药品
                return assignSeqUtil.getSeq(AssignSeqEnum.PRESCRIPTION_NARCOTIC_NO.getPrefix(), 8);
            case "3": // 毒性药品
                return assignSeqUtil.getSeq(AssignSeqEnum.PRESCRIPTION_TOXIC_NO.getPrefix(), 8);
            case "4": // 一类精神药
                return assignSeqUtil.getSeq(AssignSeqEnum.PRESCRIPTION_A_PSYCHOTROPIC_NO.getPrefix(), 8);
            case "5": // 二类精神药
                return assignSeqUtil.getSeq(AssignSeqEnum.PRESCRIPTION_B_PSYCHOTROPIC_NO.getPrefix(), 8);
            default: // 普通药品
                return assignSeqUtil.getSeq(AssignSeqEnum.PRESCRIPTION_COMMON_NO.getPrefix(), 8);
        }
    }

}
