using static StellaOps.Localization.T;
namespace StellaOps.Audit.ReplayToken;
///
/// A deterministic, content-addressable replay token with optional expiration.
///
public sealed partial class ReplayToken : IEquatable
{
public const string Scheme = "replay";
public const string DefaultAlgorithm = "SHA-256";
public const string DefaultVersion = "1.0";
///
/// Version 2.0 includes expiration support.
///
public const string VersionWithExpiration = "2.0";
///
/// Default expiration duration (1 hour).
///
public static readonly TimeSpan DefaultExpiration = TimeSpan.FromHours(1);
///
/// The token value (SHA-256 hash in hex).
///
public string Value { get; }
///
/// Algorithm used for hashing.
///
public string Algorithm { get; }
///
/// Version of the token generation algorithm.
///
public string Version { get; }
///
/// Timestamp when token was generated.
///
public DateTimeOffset GeneratedAt { get; }
///
/// Timestamp when token expires. Null means no expiration (v1.0 behavior).
///
public DateTimeOffset? ExpiresAt { get; }
///
/// Canonical representation for storage.
/// For v2.0+, includes expiration timestamp.
///
public string Canonical => ExpiresAt.HasValue
? $"{Scheme}:v{Version}:{Algorithm}:{Value}:{ExpiresAt.Value.ToUnixTimeSeconds()}"
: $"{Scheme}:v{Version}:{Algorithm}:{Value}";
///
/// Creates a replay token without expiration (v1.0 compatibility).
///
public ReplayToken(string value, DateTimeOffset generatedAt, string? algorithm = null, string? version = null)
: this(value, generatedAt, null, algorithm, version)
{
}
///
/// Creates a replay token with optional expiration.
///
public ReplayToken(string value, DateTimeOffset generatedAt, DateTimeOffset? expiresAt, string? algorithm = null, string? version = null)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException(_t("common.audit.replay_token_value_empty"), nameof(value));
}
Value = value.Trim();
GeneratedAt = generatedAt;
ExpiresAt = expiresAt;
Algorithm = string.IsNullOrWhiteSpace(algorithm) ? DefaultAlgorithm : algorithm.Trim();
if (expiresAt.HasValue && string.IsNullOrWhiteSpace(version))
{
Version = VersionWithExpiration;
}
else
{
Version = string.IsNullOrWhiteSpace(version) ? DefaultVersion : version.Trim();
}
}
}