namespace DodoSSH.Client.Domain;

/// <summary>
/// Reads the moment a version 7 identifier was created back out of it.
/// </summary>
/// <remarks>
/// <para>
/// <b>Why this exists.</b> No vault item carries a timestamp. <c>VaultItem</c> is an id, a secret, a version
/// and three sync flags, and the server's <c>created_at</c> is deliberately not handed back — so a screen
/// that wants to say when something was added has nothing to read. Every id this client mints goes through
/// <see cref="Guid.CreateVersion7()"/>, which is banned-symbol policy rather than preference (see
/// <c>BannedSymbols.txt</c>), and RFC 9562 puts 48 bits of Unix milliseconds in the first six bytes of one.
/// That is a real creation time, already stored, costing nothing.
/// </para>
/// <para>
/// <b>What it is not.</b> It is when the item was <em>created</em>, never when it was last changed — an
/// update keeps the id. A screen showing this has to say so, or it is quietly presenting a creation date as
/// a modification date. And an id minted anywhere else, by an older client or another implementation, is not
/// a v7 at all; that case answers null rather than a number derived from bytes that mean something else.
/// </para>
/// </remarks>
public static class Uuid7Timestamp
{
    /// <summary>Where the version nibble lives in the RFC byte order.</summary>
    private const int VersionByte = 6;

    /// <summary>
    /// The creation time recorded in a version 7 identifier, or null if it is not one.
    /// </summary>
    public static DateTimeOffset? Of(Guid id)
    {
        Span<byte> bytes = stackalloc byte[16];

        // Big-endian, which is the whole reason this is not two lines of shifting. Guid's own layout stores
        // its first three fields in the host's byte order, so the little-endian overload scrambles exactly
        // the six bytes being read here — and does it silently, producing dates in the year 30000 rather
        // than an error.
        if (!id.TryWriteBytes(bytes, bigEndian: true, out _))
        {
            return null;
        }

        if ((bytes[VersionByte] & 0xF0) != 0x70)
        {
            return null;
        }

        long milliseconds = 0;

        for (var i = 0; i < 6; i++)
        {
            milliseconds = (milliseconds << 8) | bytes[i];
        }

        return DateTimeOffset.FromUnixTimeMilliseconds(milliseconds);
    }
}
