<?php

namespace App\Services;

/**
 * 试卷ID生成器
 * 参考行业标准 Snowflake ID 思想
 *
 * 【修复】增强多进程并发安全：
 * - 添加进程ID区分不同 PHP-FPM Worker
 * - 增加随机数位数降低碰撞概率
 */
class PaperIdGenerator
{
    // 基准时间：2020-01-01 00:00:00 (秒)
    private const EPOCH = 1577836800;

    // 15位数字的位分配
    // 格式：时间戳(8位) + 进程ID(2位) + 序列号(2位) + 随机数(3位) = 15位数字
    private const TIMESTAMP_BITS = 35; // 时间戳（秒）- 可覆盖约34年
    private const SEQUENCE_BITS = 11;  // 序列号 - 2048个值

    /**
     * 生成15位数字ID（增强版，支持多进程并发）
     * 格式：TTTTTTTT + PP + SS + RRR（时间戳 + 进程ID + 序列号 + 随机数）
     *
     * @return string 15位数字字符串
     */
    public static function generate(): string
    {
        // 使用时间戳（分钟）而不是秒，以减少位数
        $timestamp = intdiv(time() - self::EPOCH, 60); // 从基准时间开始的分钟数
        $timestamp &= (1 << self::TIMESTAMP_BITS) - 1; // 截取指定位数

        // 获取进程ID（区分不同的 PHP-FPM Worker）
        $processId = getmypid() % 100; // 2位进程ID：00-99

        // 同一分钟内使用序列号递增，避免并发重复
        static $lastTimestamp = 0;
        static $sequence = 0;

        if ($timestamp == $lastTimestamp) {
            $sequence = ($sequence + 1) & ((1 << self::SEQUENCE_BITS) - 1);
            // 序列号用完，等待下一分钟
            if ($sequence == 0) {
                do {
                    $timestamp = intdiv(time() - self::EPOCH, 60);
                    $timestamp &= (1 << self::TIMESTAMP_BITS) - 1;
                } while ($timestamp <= $lastTimestamp);
            }
        } else {
            $sequence = 0;
        }

        $lastTimestamp = $timestamp;

        // 添加随机数后缀避免猜测（3位：000-999，碰撞概率 0.1%）
        $random = random_int(0, 999);

        // 组合：时间戳(8位) + 进程ID(2位) + 序列号(2位) + 随机数(3位) = 15位数字
        $timestampStr = str_pad((string)$timestamp, 8, '0', STR_PAD_LEFT);
        $processIdStr = str_pad((string)$processId, 2, '0', STR_PAD_LEFT);
        $sequenceStr = str_pad((string)($sequence % 100), 2, '0', STR_PAD_LEFT); // 确保只取2位
        $randomStr = str_pad((string)$random, 3, '0', STR_PAD_LEFT);

        $id = $timestampStr . $processIdStr . $sequenceStr . $randomStr;

        // 确保第一位不为0
        if ($id[0] === '0') {
            $id[0] = '1';
        }

        // 防御式校验：必须是15位且首位非0
        if (! self::validate($id)) {
            throw new \RuntimeException('生成的 paper 数字ID不合法');
        }

        return $id;
    }

    /**
     * 验证数字ID格式（15位）
     *
     * @param string $id
     * @return bool
     */
    public static function validate(string $id): bool
    {
        return preg_match('/^[1-9]\d{14}$/', $id) === 1;
    }

    /**
     * 校验完整 paper_id（paper_ + 15位数字）
     */
    public static function validatePaperId(string $paperId): bool
    {
        return preg_match('/^paper_[1-9]\d{14}$/', $paperId) === 1;
    }

    /**
     * 从ID提取时间戳
     *
     * @param string $id
     * @return int|null
     */
    public static function extractTimestamp(string $id): ?int
    {
        if (!self::validate($id)) {
            return null;
        }

        // 提取前8位时间戳（分钟），转换为秒
        $timestampMinutes = (int)substr($id, 0, 8);
        return $timestampMinutes * 60 + self::EPOCH;
    }

    /**
     * 批量生成不重复的ID
     *
     * @param int $count
     * @return array
     */
    public static function generateBatch(int $count): array
    {
        $ids = [];
        $attempts = 0;
        $maxAttempts = $count * 10;

        while (count($ids) < $count && $attempts < $maxAttempts) {
            $id = self::generate();
            if (!in_array($id, $ids)) {
                $ids[] = $id;
            }
            $attempts++;
        }

        return $ids;
    }
}
