using System;
using Svrnty.CQRS.Events.RabbitMQ.Configuration;
using System.Collections.Generic;
namespace Svrnty.CQRS.Events.RabbitMQ.Configuration;
///
/// Configuration for RabbitMQ event delivery provider.
///
public sealed class RabbitMQConfiguration
{
///
/// Gets or sets the RabbitMQ connection string.
///
///
/// Format: amqp://username:password@hostname:port/virtualhost
/// Example: amqp://guest:guest@localhost:5672/
///
public string ConnectionString { get; set; } = "amqp://guest:guest@localhost:5672/";
///
/// Gets or sets the exchange name prefix.
///
///
/// The actual exchange name will be: {ExchangePrefix}.{stream-name}
/// Example: "myapp" results in "myapp.user-events"
/// Default: Empty string (no prefix)
///
public string ExchangePrefix { get; set; } = string.Empty;
///
/// Gets or sets the default exchange type.
///
///
/// Supported values: "topic", "fanout", "direct", "headers"
/// Default: "topic" (recommended for event streaming)
///
public string DefaultExchangeType { get; set; } = "topic";
///
/// Gets or sets the default routing key strategy.
///
///
///
/// - EventTypeRoute by event type name (e.g., "UserCreatedEvent")
/// - StreamNameRoute by stream name (e.g., "user-events")
/// - WildcardRoute to all consumers (use "#" routing key for topic exchange)
///
/// Default: "EventType"
///
public string DefaultRoutingKeyStrategy { get; set; } = "EventType";
///
/// Gets or sets whether to automatically declare exchanges and queues.
///
///
/// Default: true (recommended for development)
/// Set to false in production if topology is managed externally.
///
public bool AutoDeclareTopology { get; set; } = true;
///
/// Gets or sets whether exchanges should be durable (survive broker restart).
///
///
/// Default: true (recommended for production)
///
public bool DurableExchanges { get; set; } = true;
///
/// Gets or sets whether queues should be durable.
///
///
/// Default: true (recommended for production)
///
public bool DurableQueues { get; set; } = true;
///
/// Gets or sets whether messages should be persistent.
///
///
/// Default: true (messages survive broker restart)
/// Set to false for fire-and-forget scenarios.
///
public bool PersistentMessages { get; set; } = true;
///
/// Gets or sets the prefetch count for consumers.
///
///
/// Number of unacknowledged messages each consumer can receive.
/// Higher values = better throughput, more memory usage.
/// Default: 10
///
public ushort PrefetchCount { get; set; } = 10;
///
/// Gets or sets the maximum number of connection retry attempts.
///
///
/// Default: 5
/// Set to 0 to disable retries.
///
public int MaxConnectionRetries { get; set; } = 5;
///
/// Gets or sets the delay between connection retry attempts.
///
///
/// Default: 5 seconds
/// Exponential backoff is applied.
///
public TimeSpan ConnectionRetryDelay { get; set; } = TimeSpan.FromSeconds(5);
///
/// Gets or sets the maximum number of publish retry attempts.
///
///
/// Default: 3
/// Set to 0 to disable retries.
///
public int MaxPublishRetries { get; set; } = 3;
///
/// Gets or sets the delay between publish retry attempts.
///
///
/// Default: 1 second
///
public TimeSpan PublishRetryDelay { get; set; } = TimeSpan.FromSeconds(1);
///
/// Gets or sets whether to enable publisher confirms.
///
///
/// When enabled, RabbitMQ confirms message receipt.
/// Slower but more reliable.
/// Default: true (recommended for production)
///
public bool EnablePublisherConfirms { get; set; } = true;
///
/// Gets or sets the timeout for publisher confirms.
///
///
/// Default: 5 seconds
///
public TimeSpan PublisherConfirmTimeout { get; set; } = TimeSpan.FromSeconds(5);
///
/// Gets or sets the heartbeat interval for connection health checks.
///
///
/// Default: 60 seconds
/// RabbitMQ will close connections that don't send data within 2x heartbeat.
///
public TimeSpan HeartbeatInterval { get; set; } = TimeSpan.FromSeconds(60);
///
/// Gets or sets whether to automatically recover from connection failures.
///
///
/// Default: true (recommended)
///
public bool AutoRecovery { get; set; } = true;
///
/// Gets or sets the interval between recovery attempts.
///
///
/// Default: 10 seconds
///
public TimeSpan RecoveryInterval { get; set; } = TimeSpan.FromSeconds(10);
///
/// Gets or sets additional connection properties.
///
///
/// These are sent to RabbitMQ during connection handshake.
/// Useful for debugging and monitoring.
///
public Dictionary ConnectionProperties { get; set; } = new()
{
{ "connection_name", "Svrnty.CQRS.Events" },
{ "product", "Svrnty.CQRS" }
};
///
/// Gets or sets the dead letter exchange name for failed messages.
///
///
/// If null, no dead letter exchange is configured.
/// Example: "dlx.events"
///
public string? DeadLetterExchange { get; set; }
///
/// Gets or sets the time-to-live for messages in queues.
///
///
/// If null, messages don't expire.
/// Example: TimeSpan.FromDays(7)
///
public TimeSpan? MessageTTL { get; set; }
///
/// Gets or sets the maximum queue length.
///
///
/// If null, no limit.
/// Oldest messages are dropped when limit is reached.
///
public int? MaxQueueLength { get; set; }
///
/// Validates the configuration.
///
/// Thrown if configuration is invalid.
public void Validate()
{
if (string.IsNullOrWhiteSpace(ConnectionString))
throw new InvalidOperationException("ConnectionString cannot be null or whitespace.");
if (!Uri.TryCreate(ConnectionString, UriKind.Absolute, out var uri) || uri.Scheme != "amqp" && uri.Scheme != "amqps")
throw new InvalidOperationException("ConnectionString must be a valid AMQP URI (amqp:// or amqps://).");
var validExchangeTypes = new[] { "topic", "fanout", "direct", "headers" };
if (!validExchangeTypes.Contains(DefaultExchangeType.ToLowerInvariant()))
throw new InvalidOperationException($"DefaultExchangeType must be one of: {string.Join(", ", validExchangeTypes)}");
var validRoutingStrategies = new[] { "EventType", "StreamName", "Wildcard" };
if (!validRoutingStrategies.Contains(DefaultRoutingKeyStrategy))
throw new InvalidOperationException($"DefaultRoutingKeyStrategy must be one of: {string.Join(", ", validRoutingStrategies)}");
if (PrefetchCount == 0)
throw new InvalidOperationException("PrefetchCount must be greater than 0.");
if (MaxConnectionRetries < 0)
throw new InvalidOperationException("MaxConnectionRetries cannot be negative.");
if (MaxPublishRetries < 0)
throw new InvalidOperationException("MaxPublishRetries cannot be negative.");
if (ConnectionRetryDelay <= TimeSpan.Zero)
throw new InvalidOperationException("ConnectionRetryDelay must be positive.");
if (PublishRetryDelay <= TimeSpan.Zero)
throw new InvalidOperationException("PublishRetryDelay must be positive.");
if (PublisherConfirmTimeout <= TimeSpan.Zero)
throw new InvalidOperationException("PublisherConfirmTimeout must be positive.");
if (HeartbeatInterval <= TimeSpan.Zero)
throw new InvalidOperationException("HeartbeatInterval must be positive.");
if (RecoveryInterval <= TimeSpan.Zero)
throw new InvalidOperationException("RecoveryInterval must be positive.");
if (MessageTTL.HasValue && MessageTTL.Value <= TimeSpan.Zero)
throw new InvalidOperationException("MessageTTL must be positive if specified.");
if (MaxQueueLength.HasValue && MaxQueueLength.Value <= 0)
throw new InvalidOperationException("MaxQueueLength must be positive if specified.");
}
}