using System;
using System.Text;

namespace ZB.MOM.WW.MxGateway.Worker.MxAccess;

/// <summary>
///     Derives a deterministic synthetic <see cref="Guid"/> from an alarm
///     reference for the subtag-provider fallback path, which has no native
///     MxAccess alarm GUID. Hashing the reference yields a stable identity so
///     repeated transitions for the same alarm reference correlate downstream
///     (acknowledge, snapshot, OPC UA mapping) without an alarmmgr-supplied GUID.
/// </summary>
/// <remarks>
///     The 128-bit value is computed with a fixed FNV-1a hash over the UTF-8
///     bytes of the reference, deliberately <strong>not</strong> via
///     <c>System.Security.Cryptography</c>. On .NET Framework 4.8
///     <c>MD5.Create()</c> returns the non-validated
///     <c>MD5CryptoServiceProvider</c>, whose constructor throws under the
///     Windows FIPS-compliance policy ("not part of the Windows Platform FIPS
///     validated cryptographic algorithms"). Because this derivation needs only
///     determinism and distinctness — never cryptographic strength — a plain
///     non-crypto hash avoids the FIPS gate entirely, so the subtag fallback
///     keeps working on regulated (FIPS-enabled) hosts exactly when it is needed.
/// </remarks>
internal static class SyntheticAlarmGuid
{
    // 64-bit FNV-1a constants (RFC-style; widely used reference values).
    private const ulong FnvOffsetBasis = 14695981039346656037UL;
    private const ulong FnvPrime = 1099511628211UL;

    // A second independent seed for the high 8 bytes so the full 128-bit value
    // is well-distributed across distinct references rather than two correlated
    // halves of the same single-pass hash.
    private const ulong FnvSecondSeed = 1469598103934665603UL;

    /// <summary>
    ///     Produces a stable <see cref="Guid"/> for the given alarm reference.
    ///     The same reference always maps to the same GUID; distinct references
    ///     map to distinct GUIDs with overwhelming probability.
    /// </summary>
    /// <param name="reference">
    ///     The fully-qualified alarm reference (for example
    ///     <c>"Galaxy!Area.Tag.HiHi"</c>). Treated as UTF-8 bytes.
    /// </param>
    /// <returns>A deterministic, non-empty GUID derived from the reference.</returns>
    /// <exception cref="ArgumentNullException">
    ///     Thrown when <paramref name="reference"/> is <see langword="null"/>.
    /// </exception>
    public static Guid ForReference(string reference)
    {
        if (reference is null) throw new ArgumentNullException(nameof(reference));

        byte[] bytes = Encoding.UTF8.GetBytes(reference);

        // Two independent FNV-1a passes fill the low and high 64 bits of the
        // 128-bit value. The second pass mixes the running length into its seed
        // so single-character differences and re-orderings still diverge in both
        // halves, avoiding correlated-half collisions a single pass would risk.
        ulong low = FnvOffsetBasis;
        ulong high = FnvSecondSeed;
        for (int i = 0; i < bytes.Length; i++)
        {
            byte b = bytes[i];

            low ^= b;
            low *= FnvPrime;

            high ^= unchecked(b + (ulong)i);
            high *= FnvPrime;
        }

        // Fold the length in so the empty string and other short inputs are not
        // degenerate (an all-zero / Guid.Empty result is undesirable downstream).
        low ^= (ulong)bytes.Length;
        low *= FnvPrime;

        byte[] guidBytes = new byte[16];
        WriteUInt64(guidBytes, 0, low);
        WriteUInt64(guidBytes, 8, high);
        return new Guid(guidBytes);
    }

    private static void WriteUInt64(byte[] buffer, int offset, ulong value)
    {
        for (int i = 0; i < 8; i++)
        {
            buffer[offset + i] = (byte)(value >> (i * 8));
        }
    }
}
