using System.Security.Cryptography;
using System.Text.Json;
using System.Text.Json.Serialization;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;

namespace ZB.MOM.WW.ScadaBridge.Transport.Encryption;

/// <summary>
/// T-005: computes the AES-GCM Associated Authenticated Data (AAD) for a bundle's
/// encrypted payload. AAD is the SHA-256 of the manifest after normalising the two
/// derivative fields (<c>ContentHash</c>, <c>Encryption</c>) to known sentinel
/// values — those depend on the ciphertext and the IV, so they cannot themselves
/// be authenticated, but every OTHER manifest field (<c>SourceEnvironment</c>,
/// <c>ExportedBy</c>, <c>ScadaBridgeVersion</c>, <c>Summary</c>, <c>Contents</c>,
/// <c>CreatedAtUtc</c>, …) participates in the GCM tag.
/// <para>
/// Threading this byte array through <c>AesGcm.Encrypt</c> / <c>AesGcm.Decrypt</c>
/// makes the "type the source environment name to confirm" gate
/// tamper-evident: a flipped <c>SourceEnvironment</c> on a stolen bundle yields
/// an <c>AuthenticationTagMismatchException</c> on decrypt instead of producing
/// a valid plaintext with a forged origin label.
/// </para>
/// </summary>
public static class BundleManifestAad
{
    /// <summary>
    /// JSON options matching <c>BundleSerializer.JsonOptions</c> so the AAD bytes
    /// are stable across the encrypt + decrypt side.
    /// </summary>
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        WriteIndented = false,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        Converters = { new JsonStringEnumConverter() },
    };

    /// <summary>
    /// Computes the AAD bytes for the supplied manifest. The two derivative
    /// fields are normalised to fixed sentinels so the AAD is independent of the
    /// ciphertext / IV that will eventually populate them in the on-disk
    /// manifest.
    /// </summary>
    /// <param name="manifest">The manifest whose non-derivative fields should be authenticated.</param>
    /// <returns>SHA-256 digest of the canonicalised manifest bytes.</returns>
    public static byte[] Compute(BundleManifest manifest)
    {
        ArgumentNullException.ThrowIfNull(manifest);
        var canonical = manifest with
        {
            ContentHash = string.Empty,
            Encryption = null,
        };
        var canonicalBytes = JsonSerializer.SerializeToUtf8Bytes(canonical, JsonOptions);
        return SHA256.HashData(canonicalBytes);
    }
}
