package com.zt.plat.module.qms.core.code;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import com.zt.plat.framework.tenant.core.context.TenantContextHolder;
import lombok.RequiredArgsConstructor;


@Component
@RequiredArgsConstructor
public class CodeGenUtil {
    
	private final RedisTemplate<String, String> redisTemplate;
    
    //private final RabbitMqClient rabbitMqClient;
    
    private final static String REDIS_SERIAL_NUM_KEY = "mas:serial_num";
    
    private final static String REDIS_RANDOM_NUM_KEY = "mas:random_num";
    
    /**
     * 生成编码
     * 编码生成规则，以“|”分割：/、原样输出；?、编密号；@、当前日期格式化；#、财务月日期格式化；*、流水号配置以“-”分割，第一位流水号编码(为?时需要传入动态流水号编号)，第二位流水号长度，第三位为D(以日重新计算)，Y(以年计算)，M(以月计算)，F(以财务月计算)；^、随机获取一个批量生成的编码，配置以“-”分割，第一位随机流水号编码，第二位随机流水号长度，第三位为D(以日重新计算)，Y(以年计算)，M(以月计算)，F(以财务月计算)
     * @param codeRule 编码规则
     * @return
     */
    public String genCode(String codeRule) {
        return genCode(null, "", "", codeRule);
    }
    

    /**
     * 生成编码
     * 编码生成规则，以“|”分割：/、原样输出；?、编密号；@、当前日期格式化；#、财务月日期格式化；*、流水号配置以“-”分割，第一位流水号编码(为?时需要传入动态流水号编号)，第二位流水号长度，第三位为D(以日重新计算)，Y(以年计算)，M(以月计算)，F(以财务月计算)；^、随机获取一个批量生成的编码，配置以“-”分割，第一位随机流水号编码，第二位随机流水号长度，第三位为D(以日重新计算)，Y(以年计算)，M(以月计算)，F(以财务月计算)
     * @param prefix 前缀
     * @param codeRule 编码规则
     * @return
     */
    public String genCode(String prefix, String codeRule) {
        return genCode(null, prefix, "", codeRule);
    }
    

    /**
     * 生成编码
     * 编码生成规则，以“|”分割：/、原样输出；?、编密号；@、当前日期格式化；#、财务月日期格式化；*、流水号配置以“-”分割，第一位流水号编码(为?时需要传入动态流水号编号)，第二位流水号长度，第三位为D(以日重新计算)，Y(以年计算)，M(以月计算)，F(以财务月计算)；^、随机获取一个批量生成的编码，配置以“-”分割，第一位随机流水号编码，第二位随机流水号长度，第三位为D(以日重新计算)，Y(以年计算)，M(以月计算)，F(以财务月计算)
     * @param tenantId 租户id
     * @param prefix 前缀
     * @param codeRule 编码规则
     * @return
     */
    public String genCode(String tenantId, String prefix, String codeRule) {
        return genCode(tenantId, prefix, null, codeRule);
    }

    /**
     * 动态生成编码
     * 编码生成规则，以“|”分割：/、原样输出；?、编密号；@、当前日期格式化；#、财务月日期格式化；*、流水号配置以“-”分割，第一位流水号编码(为?时需要传入动态流水号编号)，第二位流水号长度，第三位为D(以日重新计算)，Y(以年计算)，M(以月计算)，F(以财务月计算)；^、随机获取一个批量生成的编码，配置以“-”分割，第一位随机流水号编码，第二位随机流水号长度，第三位为D(以日重新计算)，Y(以年计算)，M(以月计算)，F(以财务月计算)
     * @param prefix 前缀
     * @param dynamicSerialNumCode 动态流水号编码
     * @param codeRule 编码规则
     * @return
     */
    public String genDynamicCode(String prefix, String dynamicSerialNumCode, String codeRule) {
        return genCode(null, prefix, dynamicSerialNumCode, codeRule);
    }

    /**
     * 生成编码
     * 编码生成规则，以“|”分割：/、原样输出；?、编密号；@、当前日期格式化；#、财务月日期格式化；*、流水号配置以“-”分割，第一位流水号编码(为?时需要传入动态流水号编号)，第二位流水号长度，第三位为D(以日重新计算)，Y(以年计算)，M(以月计算)，F(以财务月计算)；^、随机获取一个批量生成的编码，配置以“-”分割，第一位随机流水号编码，第二位随机流水号长度，第三位为D(以日重新计算)，Y(以年计算)，M(以月计算)，F(以财务月计算)
     * @param tenantId 租户id
     * @param prefix 前缀
     * @param dynamicSerialNumCode 动态流水号编码
     * @param codeRule 编码规则
     * @return
     */
    public String genCode(String tenantId, String prefix, String dynamicSerialNumCode, String codeRule) {
        if (StringUtils.isBlank(codeRule)) {
            return "";
        }
        if(StringUtils.isBlank(tenantId)) {
            tenantId = TenantContextHolder.getRequiredTenantId().toString();
        }
        String[] rules = codeRule.split("\\|");//竖线分割
        if (rules.length < 1) {
            return "";
        }
        StringBuffer codeBuffer = new StringBuffer(prefix);
        for (String rule : rules) {
            String ruleStart = rule.substring(0, 1);
            switch (ruleStart) {
                case "/":
                    codeBuffer.append(rule.substring(1));
                    break;
                case "@":
                    codeBuffer.append(DateUtil.format(new Date(), rule.substring(1)));
                    break;
                case "#":
                    codeBuffer.append(getFinancialYearAndMonth(rule.substring(1)));
                    break;
                case "?":
                    codeBuffer.append(SampleEncryptUtil.sampleEncrypt());
                    if (StringUtils.isNotBlank(prefix)) {
                        codeBuffer.delete(0, prefix.length());
                    }
                    break;
                case "*"://流水号
                    String serialNumRule = rule.substring(1);
                    String[] serialNumRules = serialNumRule.split("-");
                    if (serialNumRules.length != 3) {
                        throw new IllegalArgumentException("流水号规则不正确！");
                        
                    }
                    String serialNumCode = serialNumRules[0];//流水号编码
                    if("?".equals(serialNumCode)) {
                    	if (StringUtils.isBlank(dynamicSerialNumCode)) {
                    		throw new IllegalArgumentException("动态流水号编码需要必传！");
						}
                    	serialNumCode = dynamicSerialNumCode;
                    }
                    
                    Integer serialNumSeveral = Integer.parseInt(serialNumRules[1]);//流水号位数
                    String serialNumCodeRule = serialNumRules[2];//流水号生成规则
                    String serialNumRedisKey = null;
                    switch (serialNumCodeRule) {
                        case "D"://按日
                            String day = DateUtil.format(new Date(), "yyyyMMdd");
                            serialNumRedisKey = tenantId + ":" + REDIS_SERIAL_NUM_KEY + ":" + serialNumCode + ":D:" + day;
                            break;
                        case "M"://按月
                            String month = DateUtil.format(new Date(), "yyyyMM");
                            serialNumRedisKey = tenantId + ":" + REDIS_SERIAL_NUM_KEY + ":" + serialNumCode + ":M:" + month;
                            break;
                        case "Y"://按年
                            String year = DateUtil.format(new Date(), "yyyy");
                            serialNumRedisKey = tenantId + ":" + REDIS_SERIAL_NUM_KEY + ":" + serialNumCode + ":Y:" + year;
                            break;
                        case "F"://按财务月
                            String financialMonth = getFinancialYearAndMonth("yyyyMM");
                            serialNumRedisKey = tenantId + ":" + REDIS_SERIAL_NUM_KEY + ":" + serialNumCode + ":F:" + financialMonth;
                            break;
                    }
                    long serialNum = redisTemplate.opsForValue().increment(serialNumRedisKey, 1);
//                    BaseMap baseMap = new BaseMap();
//                    baseMap.put("redisKey", serialNumRedisKey);
//                    baseMap.put("redisValue", serialNum);
//                    rabbitMqClient.sendQmsSerialnumMessage(baseMap);
                    codeBuffer.append(String.format("%0"+serialNumSeveral+"d", serialNum));
                    break;
                case "^"://批量生成，随机获取
                    String batchSerialNumRule = rule.substring(1);
                    String[] batchSerialNumRules = batchSerialNumRule.split("-");
                    if (batchSerialNumRules.length != 3) {
                        throw new IllegalArgumentException("流水号规则不正确！");
                        
                    }
                    String batchSerialNumCode = batchSerialNumRules[0];//流水号编码
                    Integer batchSerialNumSeveral = Integer.parseInt(batchSerialNumRules[1]);//随机流水号位数
                    String batchSerialNumCodeRule = batchSerialNumRules[2];//流水号生成规则
                    String batchSerialNumRedisKey = null;
                    long batchSerialNumRedisKeyTimeout = 0;
                    switch (batchSerialNumCodeRule) {
                        case "D"://按日
                            String day = DateUtil.format(new Date(), "yyyyMMdd");
                            batchSerialNumRedisKey = tenantId + ":" + REDIS_RANDOM_NUM_KEY + ":" + batchSerialNumCode + ":D:" + day;
                            batchSerialNumRedisKeyTimeout = 2;
                            break;
                        case "M"://按月
                            String month = DateUtil.format(new Date(), "yyyyMM");
                            batchSerialNumRedisKey = tenantId + ":" + REDIS_RANDOM_NUM_KEY + ":" + batchSerialNumCode + ":M:" + month;
                            batchSerialNumRedisKeyTimeout = 40;
                            break;
                        case "Y"://按年
                            String year = DateUtil.format(new Date(), "yyyy");
                            batchSerialNumRedisKey = tenantId + ":" + REDIS_RANDOM_NUM_KEY + ":" + batchSerialNumCode + ":Y:" + year;
                            batchSerialNumRedisKeyTimeout = 370;
                            break;
                        case "F"://按财务月
                            String financialMonth = getFinancialYearAndMonth("yyyyMM");
                            batchSerialNumRedisKey = tenantId + ":" + REDIS_RANDOM_NUM_KEY + ":" + batchSerialNumCode + ":F:" + financialMonth;
                            batchSerialNumRedisKeyTimeout = 50;
                            break;
                    }
                    
                    String value = null;
                    if (redisTemplate.hasKey(batchSerialNumRedisKey)) {//key存在
                    	value = redisTemplate.opsForSet().pop(batchSerialNumRedisKey);
					} else {
						StringBuffer maxBuffer = new StringBuffer();
						for (int i = 0; i < batchSerialNumSeveral; i++) {
							maxBuffer.append(9);
						}
						long max = Long.valueOf(maxBuffer.toString());
						
						List<String> valueList = new ArrayList<>();
						for (int i = 1; i <= max; i++) {
							valueList.add(String.format("%0" + batchSerialNumSeveral + "d", i));
						}

						String key = batchSerialNumRedisKey;
						ListUtil.page(valueList, 1000, ls -> {
							String[] array = ls.toArray(new String[ls.size()]);
							redisTemplate.opsForSet().add(key, array);
						});			
						redisTemplate.expire(batchSerialNumRedisKey, batchSerialNumRedisKeyTimeout, TimeUnit.DAYS);

                    	value = redisTemplate.opsForSet().pop(batchSerialNumRedisKey);
					}
                    Set<String> members = redisTemplate.opsForSet().members(batchSerialNumRedisKey);
//                    BaseMap randomBaseMap = new BaseMap();
//                    randomBaseMap.put("redisKey", batchSerialNumRedisKey);
//                    randomBaseMap.put("redisRandomValue", JSON.toJSONString(members));
//                    rabbitMqClient.sendQmsSerialnumMessage(randomBaseMap);
                    codeBuffer.append(value);
                    break;
            }
         }
        return codeBuffer.toString();
    }
    
    /**
     * 获取财务年月
     * @param codeRule 财务年月格式（如：yyMM、yyyyMM、yy-MM-、yyMM-）
     * @return
     */
    public String getFinancialYearAndMonth(String codeRule) {
        String yearMonth = "";
        Calendar now = Calendar.getInstance();
        int year = now.get(Calendar.YEAR);
        int month = now.get(Calendar.MONTH) + 1;
        int day = now.get(Calendar.DAY_OF_MONTH);
        if (day <= 25 || (day <= 31 && month == 12)) {
            yearMonth = DateUtil.format(DateUtil.parse(year + "-" + month + "-01", "yyyy-MM-dd"), codeRule);
        } else {
            yearMonth = DateUtil.format(DateUtil.parse(year + "-" + (month + 1) + "-01", "yyyy-MM-dd"), codeRule);
        }
        return yearMonth;
    }
}
