namespace ZB.MOM.WW.Secrets.Abstractions;

/// <summary>
/// A validated, normalized secret name. Names are trimmed, lowercased (invariant
/// culture), and constrained to a filesystem-safe allow-list so they can double as
/// path segments and stable identifiers. Normalization happens once, at construction.
/// </summary>
public readonly record struct SecretName
{
    private const string AllowedChars = "abcdefghijklmnopqrstuvwxyz0123456789._/-";

    /// <summary>
    /// Creates a <see cref="SecretName"/> from the given raw value, normalizing then
    /// validating it.
    /// </summary>
    /// <param name="value">The raw name to normalize and validate.</param>
    /// <exception cref="ArgumentException">
    /// The value is null, empty, or whitespace; contains the path-traversal sequence
    /// <c>..</c>; is rooted (starts or ends with <c>/</c>) or contains an empty path
    /// segment (<c>//</c>); or contains any character outside <c>[a-z0-9._/-]</c> (after
    /// trim and lowercasing).
    /// </exception>
    public SecretName(string value)
    {
        string normalized = (value ?? string.Empty).Trim().ToLowerInvariant();

        if (string.IsNullOrWhiteSpace(normalized))
        {
            throw new ArgumentException(
                $"Secret name must not be null, empty, or whitespace (was '{value}').",
                nameof(value));
        }

        if (normalized.Contains("..", StringComparison.Ordinal))
        {
            throw new ArgumentException(
                $"Secret name must not contain the path-traversal sequence '..' (was '{value}').",
                nameof(value));
        }

        if (normalized.StartsWith('/') || normalized.EndsWith('/') ||
            normalized.Contains("//", StringComparison.Ordinal))
        {
            throw new ArgumentException(
                $"Secret name must not be rooted or contain an empty path segment: it may not start or end with '/' or contain '//' (was '{value}').",
                nameof(value));
        }

        foreach (char c in normalized)
        {
            if (!AllowedChars.Contains(c, StringComparison.Ordinal))
            {
                throw new ArgumentException(
                    $"Secret name contains disallowed character '{c}'; allowed characters are [a-z0-9._/-] (was '{value}').",
                    nameof(value));
            }
        }

        _value = normalized;
    }

    private readonly string? _value;

    /// <summary>The normalized secret name (trimmed, lowercased, allow-list validated).</summary>
    /// <exception cref="InvalidOperationException">The instance is the uninitialized <c>default</c> struct value.</exception>
    public string Value => _value ?? throw new InvalidOperationException("SecretName is uninitialized (default struct value).");

    /// <summary>Returns the normalized <see cref="Value"/>.</summary>
    /// <returns>The normalized secret name.</returns>
    /// <exception cref="InvalidOperationException">The instance is the uninitialized <c>default</c> struct value.</exception>
    public override string ToString() => Value;

    /// <summary>Implicitly converts a <see cref="SecretName"/> to its underlying string value.</summary>
    /// <param name="name">The secret name to convert.</param>
    /// <exception cref="InvalidOperationException">The instance is the uninitialized <c>default</c> struct value.</exception>
    public static implicit operator string(SecretName name) => name.Value;
}
