using System.Diagnostics;
using System.Security.Cryptography;
using TakeoutSaaS.Shared.Abstractions.Ids;

namespace TakeoutSaaS.Shared.Kernel.Ids;

/// <summary>
/// 基于雪花算法的长整型 ID 生成器。
/// </summary>
/// <remarks>
/// 初始化生成器。
/// </remarks>
/// <param name="workerId">工作节点 ID。</param>
/// <param name="datacenterId">机房 ID。</param>
public sealed class SnowflakeIdGenerator(long workerId = 0, long datacenterId = 0) : IIdGenerator
{
    private const long Twepoch = 1577836800000L; // 2020-01-01 UTC
    private const int WorkerIdBits = 5;
    private const int DatacenterIdBits = 5;
    private const int SequenceBits = 12;

    private const long MaxWorkerId = -1L ^ (-1L << WorkerIdBits);
    private const long MaxDatacenterId = -1L ^ (-1L << DatacenterIdBits);

    private const int WorkerIdShift = SequenceBits;
    private const int DatacenterIdShift = SequenceBits + WorkerIdBits;
    private const int TimestampLeftShift = SequenceBits + WorkerIdBits + DatacenterIdBits;
    private const long SequenceMask = -1L ^ (-1L << SequenceBits);

    private readonly long _workerId = Normalize(workerId, MaxWorkerId, nameof(workerId));
    private readonly long _datacenterId = Normalize(datacenterId, MaxDatacenterId, nameof(datacenterId));
    private long _lastTimestamp = -1L;
    private long _sequence = RandomNumberGenerator.GetInt32(0, (int)SequenceMask);
    private readonly object _syncRoot = new();


    /// <inheritdoc />
    public long NextId()
    {
        lock (_syncRoot)
        {
            var timestamp = CurrentTimeMillis();

            if (timestamp < _lastTimestamp)
            {
                // 时钟回拨时等待到下一毫秒。
                var wait = _lastTimestamp - timestamp;
                Thread.Sleep(TimeSpan.FromMilliseconds(wait));
                timestamp = CurrentTimeMillis();
                if (timestamp < _lastTimestamp)
                {
                    throw new InvalidOperationException($"系统时钟回拨 {_lastTimestamp - timestamp} 毫秒，无法生成 ID。");
                }
            }

            if (_lastTimestamp == timestamp)
            {
                _sequence = (_sequence + 1) & SequenceMask;
                if (_sequence == 0)
                {
                    timestamp = WaitNextMillis(_lastTimestamp);
                }
            }
            else
            {
                _sequence = 0;
            }

            _lastTimestamp = timestamp;

            var id = ((timestamp - Twepoch) << TimestampLeftShift)
                     | (_datacenterId << DatacenterIdShift)
                     | (_workerId << WorkerIdShift)
                     | _sequence;

            Debug.Assert(id > 0);
            return id;
        }
    }

    private static long WaitNextMillis(long lastTimestamp)
    {
        var timestamp = CurrentTimeMillis();
        while (timestamp <= lastTimestamp)
        {
            Thread.SpinWait(50);
            timestamp = CurrentTimeMillis();
        }

        return timestamp;
    }

    private static long CurrentTimeMillis() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();

    private static long Normalize(long value, long max, string name)
    {
        if (value < 0 || value > max)
        {
            throw new ArgumentOutOfRangeException(name, value, $"取值范围 0~{max}");
        }

        return value;
    }
}
