package com.sqx.modules.utils;

/**
 * 推荐码生成工具类
 */
public class PromotionCodeUtil {

    /** 自定义进制字符集（避免使用易混淆的字符） */
    private static final char[] r = new char[]{ 'M', 'J', 'U', 'D', 'Z', 'X', '9', 'C', '7', 'P',
            'E', '8', '6', 'B', 'G', 'H', 'S', '2', '5', 'F',
            'R', '4', 'Q', 'W', 'K', '3', 'V', 'Y', 'T', 'N'};

    /** 进制长度 */
    private static final int binLen = r.length;

    /** 推荐码长度 */
    private static final int codeLength = 6; // 8位长度

    /** 用于生成唯一ID的锁对象 */
    private static final Object lock = new Object();

    /** 最后生成的时间戳，用于防止短时间内生成重复码 */
    private static long lastTimestamp = -1L;

    /** 序列号，用于同一毫秒内生成多个码时保证唯一性 */
    private static long sequence = 0L;

    /** 序列号最大值 */
    private static final long MAX_SEQUENCE = 99999L;

    /** 节点ID（在集群环境中非常重要） */
    private static final int NODE_ID;

    // 静态初始化块，初始化节点ID
    static {
        int nodeId1;
        // 优先从环境变量获取节点ID
        String nodeIdStr = System.getenv("NODE_ID");
        if (nodeIdStr != null && !nodeIdStr.isEmpty()) {
            try {
                nodeId1 = Integer.parseInt(nodeIdStr) & 0x3F; // 限制在0-63之间
            } catch (NumberFormatException e) {
                nodeId1 = generateRandomNodeId();
            }
        } else {
            // 如果没有配置，生成随机节点ID
            nodeId1 = generateRandomNodeId();
        }
        NODE_ID = nodeId1;
        System.out.println("当前服务器节点ID: " + NODE_ID);
    }

    /**
     * 生成随机节点ID（当环境变量未配置时使用）
     */
    private static int generateRandomNodeId() {
        // 使用服务器信息和当前时间生成相对唯一的节点ID
        try {
            String hostName = java.net.InetAddress.getLocalHost().getHostName();
            int hashCode = hostName.hashCode();
            // 结合当前时间戳的低8位，增加随机性
            long timestamp = System.currentTimeMillis() & 0xFF;
            return (Math.abs(hashCode) ^ (int)timestamp) & 0x3F; // 限制在0-63之间
        } catch (Exception e) {
            // 如果获取主机名失败，使用纯随机数
            return (int)(Math.random() * 64);
        }
    }

    /**
     * 生成唯一推荐码（支持集群环境）
     * @return 唯一的推荐码字符串
     */
    public static String generatePromotionCode() {
        // 生成基础唯一数值
        long uniqueValue = generateUniqueValue();

        // 使用进制转换算法
        StringBuilder sb = new StringBuilder();
        while (uniqueValue > 0) {
            int ind = (int) (uniqueValue % binLen);
            sb.append(r[ind]);
            uniqueValue = uniqueValue / binLen;
        }

        // 确保推荐码长度
        String code = sb.toString();
        if (code.length() < codeLength) {
            // 使用随机字符填充前面
            StringBuilder padding = new StringBuilder();
            int paddingLength = codeLength - code.length();
            for (int i = 0; i < paddingLength; i++) {
                long randomSeed = System.nanoTime() + sequence;
                int randomIndex = (int) (Math.abs(randomSeed) % binLen);
                padding.append(r[randomIndex]);
            }
            code = padding.append(sb).toString();
        } else if (code.length() > codeLength) {
            // 如果超过长度，截取前codeLength位
            code = code.substring(0, codeLength);
        }

        return code;
    }

    /**
     * 生成唯一数值，结合时间戳、节点ID和序列号
     * @return 唯一数值
     */
    private static long generateUniqueValue() {
        synchronized (lock) {
            long timestamp = System.currentTimeMillis();

            // 如果是同一毫秒，序列号加1
            if (timestamp == lastTimestamp) {
                sequence++;
                // 防止序列号溢出
                if (sequence > MAX_SEQUENCE) {
                    // 等待下一毫秒
                    while (System.currentTimeMillis() <= timestamp) {
                        Thread.yield();
                    }
                    timestamp = System.currentTimeMillis();
                    sequence = 0L;
                }
            } else {
                // 不同毫秒，序列号重置
                sequence = 0L;
            }

            lastTimestamp = timestamp;

            // 将节点ID纳入唯一性计算
            // 格式：时间戳(41位) + 节点ID(6位) + 序列号(17位)
            long result = (timestamp << 23);
            result |= ((long)NODE_ID << 17);
            result |= (sequence & 0x1FFFF); // 17位序列号

            return Math.abs(result);
        }
    }
}
