using System.Collections.Immutable; using System.Text.Json.Serialization; namespace StellaOps.Notify.Models; /// /// Quiet hours schedule configuration for suppressing notifications during specified periods. /// public sealed record NotifyQuietHoursSchedule { [JsonConstructor] public NotifyQuietHoursSchedule( string scheduleId, string tenantId, string name, string cronExpression, TimeSpan duration, string timeZone, string? channelId = null, bool enabled = true, string? description = null, ImmutableDictionary? metadata = null, string? createdBy = null, DateTimeOffset? createdAt = null, string? updatedBy = null, DateTimeOffset? updatedAt = null) { ScheduleId = NotifyValidation.EnsureNotNullOrWhiteSpace(scheduleId, nameof(scheduleId)); TenantId = NotifyValidation.EnsureNotNullOrWhiteSpace(tenantId, nameof(tenantId)); Name = NotifyValidation.EnsureNotNullOrWhiteSpace(name, nameof(name)); CronExpression = NotifyValidation.EnsureNotNullOrWhiteSpace(cronExpression, nameof(cronExpression)); Duration = duration > TimeSpan.Zero ? duration : TimeSpan.FromHours(8); TimeZone = NotifyValidation.EnsureNotNullOrWhiteSpace(timeZone, nameof(timeZone)); ChannelId = NotifyValidation.TrimToNull(channelId); Enabled = enabled; Description = NotifyValidation.TrimToNull(description); Metadata = NotifyValidation.NormalizeStringDictionary(metadata); CreatedBy = NotifyValidation.TrimToNull(createdBy); CreatedAt = NotifyValidation.EnsureUtc(createdAt ?? DateTimeOffset.UtcNow); UpdatedBy = NotifyValidation.TrimToNull(updatedBy); UpdatedAt = NotifyValidation.EnsureUtc(updatedAt ?? CreatedAt); } public static NotifyQuietHoursSchedule Create( string scheduleId, string tenantId, string name, string cronExpression, TimeSpan duration, string timeZone, string? channelId = null, bool enabled = true, string? description = null, IEnumerable>? metadata = null, string? createdBy = null, DateTimeOffset? createdAt = null, string? updatedBy = null, DateTimeOffset? updatedAt = null) { return new NotifyQuietHoursSchedule( scheduleId, tenantId, name, cronExpression, duration, timeZone, channelId, enabled, description, ToImmutableDictionary(metadata), createdBy, createdAt, updatedBy, updatedAt); } public string ScheduleId { get; } public string TenantId { get; } public string Name { get; } /// /// Cron expression defining when quiet hours start. /// public string CronExpression { get; } /// /// Duration of the quiet hours window. /// public TimeSpan Duration { get; } /// /// IANA time zone for evaluating the cron expression (e.g., "America/New_York"). /// public string TimeZone { get; } /// /// Optional channel ID to scope quiet hours to a specific channel. /// If null, applies to all channels. /// public string? ChannelId { get; } public bool Enabled { get; } public string? Description { get; } public ImmutableDictionary Metadata { get; } public string? CreatedBy { get; } public DateTimeOffset CreatedAt { get; } public string? UpdatedBy { get; } public DateTimeOffset UpdatedAt { get; } private static ImmutableDictionary? ToImmutableDictionary(IEnumerable>? pairs) { if (pairs is null) { return null; } var builder = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); foreach (var (key, value) in pairs) { builder[key] = value; } return builder.ToImmutable(); } } /// /// Maintenance window for planned suppression of notifications. /// public sealed record NotifyMaintenanceWindow { [JsonConstructor] public NotifyMaintenanceWindow( string windowId, string tenantId, string name, DateTimeOffset startsAt, DateTimeOffset endsAt, bool suppressNotifications = true, string? reason = null, ImmutableArray channelIds = default, ImmutableArray ruleIds = default, ImmutableDictionary? metadata = null, string? createdBy = null, DateTimeOffset? createdAt = null, string? updatedBy = null, DateTimeOffset? updatedAt = null) { WindowId = NotifyValidation.EnsureNotNullOrWhiteSpace(windowId, nameof(windowId)); TenantId = NotifyValidation.EnsureNotNullOrWhiteSpace(tenantId, nameof(tenantId)); Name = NotifyValidation.EnsureNotNullOrWhiteSpace(name, nameof(name)); StartsAt = NotifyValidation.EnsureUtc(startsAt); EndsAt = NotifyValidation.EnsureUtc(endsAt); SuppressNotifications = suppressNotifications; Reason = NotifyValidation.TrimToNull(reason); ChannelIds = NormalizeStringArray(channelIds); RuleIds = NormalizeStringArray(ruleIds); Metadata = NotifyValidation.NormalizeStringDictionary(metadata); CreatedBy = NotifyValidation.TrimToNull(createdBy); CreatedAt = NotifyValidation.EnsureUtc(createdAt ?? DateTimeOffset.UtcNow); UpdatedBy = NotifyValidation.TrimToNull(updatedBy); UpdatedAt = NotifyValidation.EnsureUtc(updatedAt ?? CreatedAt); if (EndsAt <= StartsAt) { throw new ArgumentException("EndsAt must be after StartsAt.", nameof(endsAt)); } } public static NotifyMaintenanceWindow Create( string windowId, string tenantId, string name, DateTimeOffset startsAt, DateTimeOffset endsAt, bool suppressNotifications = true, string? reason = null, IEnumerable? channelIds = null, IEnumerable? ruleIds = null, IEnumerable>? metadata = null, string? createdBy = null, DateTimeOffset? createdAt = null, string? updatedBy = null, DateTimeOffset? updatedAt = null) { return new NotifyMaintenanceWindow( windowId, tenantId, name, startsAt, endsAt, suppressNotifications, reason, ToImmutableArray(channelIds), ToImmutableArray(ruleIds), ToImmutableDictionary(metadata), createdBy, createdAt, updatedBy, updatedAt); } public string WindowId { get; } public string TenantId { get; } public string Name { get; } public DateTimeOffset StartsAt { get; } public DateTimeOffset EndsAt { get; } /// /// Whether to suppress notifications during the maintenance window. /// public bool SuppressNotifications { get; } /// /// Reason for the maintenance window. /// public string? Reason { get; } /// /// Optional list of channel IDs to scope the maintenance window. /// If empty, applies to all channels. /// public ImmutableArray ChannelIds { get; } /// /// Optional list of rule IDs to scope the maintenance window. /// If empty, applies to all rules. /// public ImmutableArray RuleIds { get; } public ImmutableDictionary Metadata { get; } public string? CreatedBy { get; } public DateTimeOffset CreatedAt { get; } public string? UpdatedBy { get; } public DateTimeOffset UpdatedAt { get; } /// /// Checks if the maintenance window is active at the specified time. /// public bool IsActiveAt(DateTimeOffset timestamp) => SuppressNotifications && timestamp >= StartsAt && timestamp < EndsAt; private static ImmutableArray NormalizeStringArray(ImmutableArray values) { if (values.IsDefaultOrEmpty) { return ImmutableArray.Empty; } return values .Where(static v => !string.IsNullOrWhiteSpace(v)) .Select(static v => v.Trim()) .Distinct(StringComparer.Ordinal) .ToImmutableArray(); } private static ImmutableArray ToImmutableArray(IEnumerable? values) => values is null ? ImmutableArray.Empty : values.ToImmutableArray(); private static ImmutableDictionary? ToImmutableDictionary(IEnumerable>? pairs) { if (pairs is null) { return null; } var builder = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); foreach (var (key, value) in pairs) { builder[key] = value; } return builder.ToImmutable(); } } /// /// Operator override for quiet hours or throttle configuration. /// Allows an operator to temporarily bypass quiet hours or throttling. /// public sealed record NotifyOperatorOverride { [JsonConstructor] public NotifyOperatorOverride( string overrideId, string tenantId, NotifyOverrideType overrideType, DateTimeOffset expiresAt, string? channelId = null, string? ruleId = null, string? reason = null, string? createdBy = null, DateTimeOffset? createdAt = null) { OverrideId = NotifyValidation.EnsureNotNullOrWhiteSpace(overrideId, nameof(overrideId)); TenantId = NotifyValidation.EnsureNotNullOrWhiteSpace(tenantId, nameof(tenantId)); OverrideType = overrideType; ExpiresAt = NotifyValidation.EnsureUtc(expiresAt); ChannelId = NotifyValidation.TrimToNull(channelId); RuleId = NotifyValidation.TrimToNull(ruleId); Reason = NotifyValidation.TrimToNull(reason); CreatedBy = NotifyValidation.TrimToNull(createdBy); CreatedAt = NotifyValidation.EnsureUtc(createdAt ?? DateTimeOffset.UtcNow); } public static NotifyOperatorOverride Create( string overrideId, string tenantId, NotifyOverrideType overrideType, DateTimeOffset expiresAt, string? channelId = null, string? ruleId = null, string? reason = null, string? createdBy = null, DateTimeOffset? createdAt = null) { return new NotifyOperatorOverride( overrideId, tenantId, overrideType, expiresAt, channelId, ruleId, reason, createdBy, createdAt); } public string OverrideId { get; } public string TenantId { get; } public NotifyOverrideType OverrideType { get; } public DateTimeOffset ExpiresAt { get; } /// /// Optional channel ID to scope the override. /// public string? ChannelId { get; } /// /// Optional rule ID to scope the override. /// public string? RuleId { get; } public string? Reason { get; } public string? CreatedBy { get; } public DateTimeOffset CreatedAt { get; } /// /// Checks if the override is active at the specified time. /// public bool IsActiveAt(DateTimeOffset timestamp) => timestamp < ExpiresAt; } /// /// Type of operator override. /// [JsonConverter(typeof(JsonStringEnumConverter))] public enum NotifyOverrideType { /// /// Bypass quiet hours. /// BypassQuietHours, /// /// Bypass throttling. /// BypassThrottle, /// /// Bypass maintenance window. /// BypassMaintenance, /// /// Force suppress notifications. /// ForceSuppression }