using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using RabbitMQ.Client.Exceptions;

public sealed class RabbitMqTopologyProvisioner : IHostedService
{
    public const string Exchange   = "clinical.notifications.exchange";
    public const string PagingKey  = "alerts.paging";
    public const string EscalKey   = "alerts.escalation";
    public const string DischargeKey = "notifications.discharge";
    public const string ReconciliationKey = "notifications.reconciliation";
    public const string SyncExchange = "clinical.sync";
    public const string SyncBatchReceivedKey = "sync.batch_received";
    public const string SyncBatchQueue = "clinical.sync.batch_received";

    private readonly RabbitMqOptions _opts;
    private readonly IHostEnvironment _env;
    private readonly ILogger<RabbitMqTopologyProvisioner> _logger;

    public RabbitMqTopologyProvisioner(
        IOptions<RabbitMqOptions> opts,
        IHostEnvironment env,
        ILogger<RabbitMqTopologyProvisioner> logger)
    {
        _opts   = opts.Value;
        _env    = env;
        _logger = logger;
    }

    public Task StartAsync(CancellationToken ct)
    {
        var factory = BuildFactory();
        using var connection = factory.CreateConnection();
        using var channel    = connection.CreateModel();

        if (_env.IsDevelopment() || _env.IsEnvironment("Testing"))
        {
            // Use a throwaway channel — a failed purge on a missing queue closes the
            // channel, which would break the declare calls below.
            using var cleanup = connection.CreateModel();

            try
            {
                // x-message-ttl is immutable once the queue exists. Integration tests
                // use 5 s while local dev uses 5 min — delete so config drives the TTL.
                cleanup.QueueDelete("alerts.paging.dlq", ifUnused: false, ifEmpty: false);
            }
            catch (OperationInterruptedException ex)
            {
                _logger.LogDebug(ex, "DLQ delete skipped — queue may not exist yet");
            }

            if (_env.IsEnvironment("Testing"))
            {
                foreach (var queue in new[] { "alerts.paging.queue", "alerts.escalation.queue" })
                {
                    try { cleanup.QueuePurge(queue); }
                    catch (OperationInterruptedException ex)
                    {
                        _logger.LogDebug(ex, "Queue purge skipped for {Queue}", queue);
                    }
                }
            }
        }

        // Direct exchange — routing key determines destination queue.
        channel.ExchangeDeclare(Exchange, ExchangeType.Direct, durable: true);

        // --- alerts.paging.queue ---
        // Dead-letters to the default exchange with routing key = alerts.paging.dlq.
        // Prefetch is set per-consumer, not here.
        channel.QueueDeclare(
            queue:      "alerts.paging.queue",
            durable:    true,
            exclusive:  false,
            autoDelete: false,
            arguments: new Dictionary<string, object>
            {
                ["x-dead-letter-exchange"]     = "",                  // default exchange
                ["x-dead-letter-routing-key"]  = "alerts.paging.dlq",
            });
        channel.QueueBind("alerts.paging.queue", Exchange, PagingKey);

        // --- alerts.paging.dlq ---
        // Messages land here after NACK from the paging worker.
        // After x-message-ttl expires, re-routes to clinical.notifications.exchange
        // with routing key alerts.escalation → reaches alerts.escalation.queue.
        DeclarePagingDlq(connection, channel);
        // DLQ is reached via the default exchange — no binding to the direct exchange needed.

        // --- alerts.escalation.queue ---
        channel.QueueDeclare(
            queue:      "alerts.escalation.queue",
            durable:    true,
            exclusive:  false,
            autoDelete: false,
            arguments: null);
        channel.QueueBind("alerts.escalation.queue", Exchange, EscalKey);

        // --- notifications.discharge.queue ---
        channel.QueueDeclare(
            queue:      "notifications.discharge.queue",
            durable:    true,
            exclusive:  false,
            autoDelete: false,
            arguments: null);
        channel.QueueBind("notifications.discharge.queue", Exchange, DischargeKey);

        // --- notifications.appointment.queue (placeholder) ---
        channel.QueueDeclare(
            queue:      "notifications.appointment.queue",
            durable:    true,
            exclusive:  false,
            autoDelete: false,
            arguments: null);
        channel.QueueBind("notifications.appointment.queue", Exchange, "notifications.appointment");

        channel.QueueDeclare(
            queue:      "notifications.reconciliation.queue",
            durable:    true,
            exclusive:  false,
            autoDelete: false,
            arguments: null);
        channel.QueueBind("notifications.reconciliation.queue", Exchange, ReconciliationKey);

        channel.ExchangeDeclare(SyncExchange, ExchangeType.Topic, durable: true);

        channel.QueueDeclare(
            queue:      SyncBatchQueue,
            durable:    true,
            exclusive:  false,
            autoDelete: false,
            arguments:  null);
        channel.QueueBind(SyncBatchQueue, SyncExchange, SyncBatchReceivedKey);

        _logger.LogInformation(
            "RabbitMQ topology provisioned. Exchange={Exchange} PagingDlqTtlMs={Ttl}",
            Exchange, _opts.PagingAckTimeoutMs);

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken ct) => Task.CompletedTask;

    private void DeclarePagingDlq(IConnection connection, IModel channel)
    {
        const string dlq = "alerts.paging.dlq";
        var args = new Dictionary<string, object>
        {
            ["x-message-ttl"] = _opts.PagingAckTimeoutMs,
            ["x-dead-letter-exchange"] = Exchange,
            ["x-dead-letter-routing-key"] = EscalKey,
        };

        try
        {
            channel.QueueDeclare(dlq, durable: true, exclusive: false, autoDelete: false, arguments: args);
        }
        catch (OperationInterruptedException ex)
        {
            // Another process (e.g. local dev API) may have created the DLQ with a different
            // x-message-ttl. Delete and recreate so PagingAckTimeoutMs drives escalation timing.
            _logger.LogWarning(ex,
                "Paging DLQ arguments mismatch — deleting and recreating with PagingDlqTtlMs={Ttl}",
                _opts.PagingAckTimeoutMs);

            using var cleanup = connection.CreateModel();
            cleanup.QueueDelete(dlq, ifUnused: false, ifEmpty: false);
            cleanup.QueueDeclare(dlq, durable: true, exclusive: false, autoDelete: false, arguments: args);
        }
    }

    public IConnectionFactory BuildFactory() =>
        RabbitMqConnectionFactory.Create(_opts, dispatchConsumersAsync: true);
}