using System.Security.Cryptography;
using ZB.MOM.WW.Secrets.Abstractions;

namespace ZB.MOM.WW.Secrets.MasterKey;

/// <summary>
/// Shared base for the concrete <see cref="IMasterKeyProvider"/> implementations. Centralizes the
/// 32-byte key-length validation and the non-secret <see cref="KekId"/> derivation so every source
/// (environment, file, DPAPI) fails closed identically and reports a stable identifier.
/// </summary>
/// <remarks>
/// The key is re-resolved on every <see cref="GetMasterKey"/> call so that a rotated environment or
/// file value takes effect without recreating the provider. <see cref="KekId"/> is likewise derived
/// from the currently resolved key (unless an explicit id is configured), so it stays stable for a
/// given key and shifts only when the key itself changes.
/// </remarks>
public abstract class MasterKeyProviderBase : IMasterKeyProvider
{
    /// <summary>The required master-key length in bytes (AES-256).</summary>
    protected const int KeySizeBytes = 32;

    private readonly MasterKeyOptions _options;

    /// <summary>Creates the base provider bound to the given <paramref name="options"/>.</summary>
    /// <param name="options">The master-key configuration.</param>
    protected MasterKeyProviderBase(MasterKeyOptions options)
    {
        _options = options ?? throw new ArgumentNullException(nameof(options));
    }

    /// <summary>The master-key configuration this provider was created with.</summary>
    protected MasterKeyOptions Options => _options;

    /// <inheritdoc />
    public ReadOnlyMemory<byte> GetMasterKey() => ResolveValidatedKey();

    /// <inheritdoc />
    public string KekId
    {
        get
        {
            string? explicitId = _options.KekId;
            if (!string.IsNullOrEmpty(explicitId))
            {
                return explicitId;
            }

            return DeriveKekId(ResolveValidatedKey());
        }
    }

    /// <summary>
    /// Resolves the raw key material from the concrete source. Implementations throw
    /// <see cref="MasterKeyUnavailableException"/> when the source is missing or malformed; length
    /// validation is applied by the base class.
    /// </summary>
    /// <returns>The resolved key bytes (any length; validated by the caller).</returns>
    protected abstract byte[] ResolveKeyBytes();

    private byte[] ResolveValidatedKey()
    {
        byte[] key = ResolveKeyBytes();
        if (key.Length != KeySizeBytes)
        {
            throw new MasterKeyUnavailableException(
                $"Master key must be exactly {KeySizeBytes} bytes (AES-256) but was {key.Length} bytes.");
        }

        return key;
    }

    private static string DeriveKekId(byte[] key)
    {
        // Non-secret, deterministic id: a truncated SHA-256 of the key. The hash is one-way and
        // truncated, so it identifies the key without revealing any usable key material.
        string hex = Convert.ToHexString(SHA256.HashData(key));
        return "sha256:" + hex[..12].ToLowerInvariant();
    }
}
