using System;
using System.Collections.Generic;
using System.Linq;
namespace Svrnty.CQRS.Events.Abstractions.Subscriptions;
///
/// Represents a persistent subscription to correlated events.
/// Survives client disconnection and delivers missed events on reconnect.
///
public sealed class PersistentSubscription
{
///
/// Unique subscription identifier.
///
public required string Id { get; init; }
///
/// User/client identifier who owns this subscription.
///
public required string SubscriberId { get; init; }
///
/// Correlation ID to filter events by.
/// Only events with this correlation ID will be delivered.
///
public required string CorrelationId { get; init; }
///
/// Event type names the subscriber wants to receive.
/// If empty or null, all events for the correlation are delivered.
///
public HashSet EventTypes { get; init; } = new();
///
/// Event types that complete/close the subscription.
/// When one of these events is delivered, the subscription is marked as Completed.
///
public HashSet TerminalEventTypes { get; init; } = new();
///
/// How events should be delivered to the client.
///
public DeliveryMode DeliveryMode { get; init; } = DeliveryMode.Immediate;
///
/// When the subscription was created.
///
public DateTimeOffset CreatedAt { get; init; }
///
/// Optional expiration time for the subscription.
/// If set, subscription will be marked as Expired after this time.
///
public DateTimeOffset? ExpiresAt { get; init; }
///
/// When the subscription completed, expired, or was cancelled.
///
public DateTimeOffset? CompletedAt { get; private set; }
///
/// Last event sequence number successfully delivered to the client.
/// Used for catch-up on reconnect.
///
public long LastDeliveredSequence { get; private set; }
///
/// Current status of the subscription.
///
public SubscriptionStatus Status { get; private set; } = SubscriptionStatus.Active;
///
/// Optional connection ID if client is currently connected.
///
public string? ConnectionId { get; set; }
///
/// Optional data source ID for client-side routing.
///
public string? DataSourceId { get; init; }
///
/// Mark a sequence number as successfully delivered.
///
public void MarkDelivered(long sequence)
{
if (sequence > LastDeliveredSequence)
{
LastDeliveredSequence = sequence;
}
}
///
/// Mark subscription as completed (terminal event received).
///
public void Complete()
{
if (Status == SubscriptionStatus.Active || Status == SubscriptionStatus.Paused)
{
Status = SubscriptionStatus.Completed;
CompletedAt = DateTimeOffset.UtcNow;
}
}
///
/// Mark subscription as cancelled by user.
///
public void Cancel()
{
if (Status == SubscriptionStatus.Active || Status == SubscriptionStatus.Paused)
{
Status = SubscriptionStatus.Cancelled;
CompletedAt = DateTimeOffset.UtcNow;
}
}
///
/// Mark subscription as expired (TTL reached).
///
public void Expire()
{
if (Status == SubscriptionStatus.Active || Status == SubscriptionStatus.Paused)
{
Status = SubscriptionStatus.Expired;
CompletedAt = DateTimeOffset.UtcNow;
}
}
///
/// Pause the subscription (stops event delivery).
///
public void Pause()
{
if (Status == SubscriptionStatus.Active)
{
Status = SubscriptionStatus.Paused;
}
}
///
/// Resume a paused subscription.
///
public void Resume()
{
if (Status == SubscriptionStatus.Paused)
{
Status = SubscriptionStatus.Active;
}
}
///
/// Check if the subscription has expired.
///
public bool IsExpired => ExpiresAt.HasValue && DateTimeOffset.UtcNow > ExpiresAt.Value;
///
/// Check if this event type should be delivered to the subscriber.
///
public bool ShouldDeliverEventType(string eventTypeName)
{
// If no filter specified, deliver all events
if (EventTypes == null || EventTypes.Count == 0)
return true;
// Check if event type is in the filter list
return EventTypes.Contains(eventTypeName);
}
///
/// Check if this event type is a terminal event.
///
public bool IsTerminalEvent(string eventTypeName)
{
return TerminalEventTypes != null && TerminalEventTypes.Contains(eventTypeName);
}
///
/// Check if subscription can receive events.
///
public bool CanReceiveEvents => Status == SubscriptionStatus.Active;
}