package cn.novalon.gym.manage.common.util;

import cn.novalon.gym.manage.common.exception.ErrorCode;
import cn.novalon.gym.manage.common.exception.SystemException;
import cn.novalon.gym.manage.common.exception.ValidationException;

import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.LockSupport;

/**
 * 雪花算法ID生成器
 * 
 * 文件定义：基于Twitter Snowflake算法的分布式唯一ID生成器
 * 涉及业务：为系统所有实体生成唯一ID，支持分布式环境下的ID生成
 * 算法：使用雪花算法，结合时间戳、机器ID和序列号生成唯一ID，支持高并发场景
 * 
 * @author 张翔
 * @date 2026-03-13
 */
public final class SnowflakeId {

    private static final int DEFAULT_WORKER_BITS = 10;
    private static final int DEFAULT_SEQ_BITS = 12;
    private static final long DEFAULT_EPOCH = 1582136402000L;
    private static final int MAX_RETRIES = 10;
    private static final long MAX_BACKWARD_MS = 50;
    private static final int SPIN_THRESHOLD = 5;
    private static final long TIME_CACHE_DURATION_MS = 16;

    private static final AtomicLong lastTimestamp = new AtomicLong(-1L);
    private static final AtomicLong sequence = new AtomicLong(0);
    private static volatile SnowflakeConfig config;
    private static volatile long workerId;
    private static volatile long lastTimeCacheMs;
    private static volatile int timeCacheHits;

    static {
        configure(DEFAULT_WORKER_BITS, DEFAULT_SEQ_BITS, DEFAULT_EPOCH);
    }

    private static void configure(int workerBits, int seqBits, long epoch) {
        validateBits(workerBits, seqBits);
        config = new SnowflakeConfig(epoch, workerBits, seqBits);
        workerId = resolveWorkerId(config.maxWorkerId);
        lastTimeCacheMs = 0;
        timeCacheHits = 0;
    }

    public static long nextId() {
        for (int i = 0; i < MAX_RETRIES; i++) {
            try {
                return nextIdInternal();
            } catch (ClockBackwardException e) {
                long backwardMs = e.getBackwardMs();
                if (backwardMs > MAX_BACKWARD_MS) {
                    throw e;
                }
                if (i < SPIN_THRESHOLD) {
                    LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
                } else {
                    LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(10));
                }
            }
        }
        throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, 
                "Failed to generate ID after " + MAX_RETRIES + " retries");
    }

    private static long nextIdInternal() {
        long currentTs = timeGen();
        long lastTs;
        long seq;

        do {
            lastTs = lastTimestamp.get();

            if (currentTs < lastTs) {
                long backwardMs = lastTs - currentTs;
                if (backwardMs <= MAX_BACKWARD_MS) {
                    lastTimestamp.set(currentTs);
                    lastTs = currentTs;
                } else {
                    throw new ClockBackwardException(backwardMs);
                }
            }

            if (currentTs == lastTs) {
                seq = sequence.incrementAndGet() & config.sequenceMask;
                if (seq == 0) {
                    currentTs = waitNextMillis(currentTs);
                }
            } else {
                seq = 0;
            }
        } while (!lastTimestamp.compareAndSet(lastTs, currentTs));

        return ((currentTs - config.epoch) << config.timestampShift)
                | (workerId << config.workerShift)
                | seq;
    }

    private static long waitNextMillis(long currentTs) {
        long deadline = currentTs + 2;
        int spinCount = 0;

        while (currentTs <= lastTimestamp.get()) {
            if (currentTs >= deadline) {
                return currentTs;
            }

            if (spinCount < 10) {
                spinCount++;
            } else if (spinCount < 50) {
                LockSupport.parkNanos(100_000);
                spinCount++;
            } else {
                LockSupport.parkNanos(500_000);
            }
            currentTs = timeGen();
        }
        return currentTs;
    }

    private static long timeGen() {
        long now = System.currentTimeMillis();
        long cached = lastTimeCacheMs;

        if (now - cached < TIME_CACHE_DURATION_MS) {
            timeCacheHits++;
            return cached;
        }

        synchronized (SnowflakeId.class) {
            cached = lastTimeCacheMs;
            if (now - cached < TIME_CACHE_DURATION_MS) {
                timeCacheHits++;
                return cached;
            }
            lastTimeCacheMs = now;
            return now;
        }
    }

    public static int getTimeCacheHits() {
        return timeCacheHits;
    }

    public static void resetTimeCache() {
        synchronized (SnowflakeId.class) {
            lastTimeCacheMs = 0;
            timeCacheHits = 0;
        }
    }

    private static void validateBits(int workerBits, int seqBits) {
        if (workerBits < 0 || workerBits > 22) {
            throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE, "WorkerID位数必须在0-22之间");
        }
        if (seqBits < 0 || seqBits > 22) {
            throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE, "序列号位数必须在0-22之间");
        }
        if (workerBits + seqBits > 22) {
            throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE, 
                    "WorkerID和序列号位数总和不能超过22位，当前为: " + (workerBits + seqBits));
        }
        if (workerBits + seqBits == 0) {
            throw new ValidationException(ErrorCode.VALIDATION_INVALID_VALUE, "WorkerID和序列号位数总和不能为0");
        }
    }

    private static long resolveWorkerId(long maxWorkerId) {
        long id = generateNewId();
        if (id < 0 || id > maxWorkerId) {
            throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, 
                    "WorkerID超出有效范围: " + id + " (有效范围: 0-" + maxWorkerId + ")");
        }
        return id;
    }

    private static long generateNewId() {
        long newId = ThreadLocalRandom.current().nextLong(config.maxWorkerId + 1);
        return newId;
    }

    public static void config(int workerBits, int seqBits, long epoch) {
        configure(workerBits, seqBits, epoch);
    }

    public static long getWorkerId() {
        return workerId;
    }

    private static class SnowflakeConfig {
        final long epoch;
        final int timestampShift;
        final int workerShift;
        final long sequenceMask;
        final long maxWorkerId;

        SnowflakeConfig(long epoch, int workerBits, int seqBits) {
            this.epoch = epoch;
            this.timestampShift = workerBits + seqBits;
            this.workerShift = seqBits;
            this.sequenceMask = ~(-1L << seqBits);
            this.maxWorkerId = ~(-1L << workerBits);
        }
    }

    public static class ClockBackwardException extends RuntimeException {
        private static final long serialVersionUID = 1L;
        private final long backwardMs;

        ClockBackwardException(long backwardMs) {
            super("Clock moved backwards by " + backwardMs + "ms");
            this.backwardMs = backwardMs;
        }

        public long getBackwardMs() {
            return backwardMs;
        }
    }
}
