import { describe, expect, vi } from "vitest";
import { redisTest } from "@internal/testcontainers";
import { z } from "zod";
import {
  FairQueue,
  DefaultFairQueueKeyProducer,
  DRRScheduler,
  ConcurrencyManager,
  VisibilityManager,
  MasterQueue,
  WorkerQueueManager,
  FixedDelayRetry,
} from "../index.js";
import type { FairQueueKeyProducer, FairQueueOptions, QueueDescriptor } from "../types.js";
import { createRedisClient, type RedisOptions } from "@internal/redis";

const TestPayloadSchema = z.object({ id: z.number(), value: z.string() });
type TestPayload = z.infer<typeof TestPayloadSchema>;

// Constant for test worker queue ID
const TEST_WORKER_QUEUE_ID = "test-worker-queue";

/**
 * TestFairQueueHelper wraps FairQueue for easier testing in race condition tests.
 */
class TestFairQueueHelper {
  public fairQueue: FairQueue<typeof TestPayloadSchema>;
  private workerQueueManager: WorkerQueueManager;
  private isRunning = false;
  private abortController: AbortController;
  private consumerLoops: Promise<void>[] = [];
  private messageHandler?: (ctx: {
    message: {
      id: string;
      queueId: string;
      payload: TestPayload;
      timestamp: number;
      attempt: number;
    };
    queue: { id: string; tenantId: string };
    consumerId: string;
    heartbeat: () => Promise<boolean>;
    complete: () => Promise<void>;
    release: () => Promise<void>;
    fail: (error?: Error) => Promise<void>;
  }) => Promise<void>;

  constructor(
    private redisOptions: RedisOptions,
    private keys: FairQueueKeyProducer,
    options: Omit<FairQueueOptions<typeof TestPayloadSchema>, "redis" | "keys" | "workerQueue">
  ) {
    this.abortController = new AbortController();

    this.fairQueue = new FairQueue({
      ...options,
      redis: redisOptions,
      keys,
      workerQueue: {
        resolveWorkerQueue: () => TEST_WORKER_QUEUE_ID,
      },
    });

    this.workerQueueManager = new WorkerQueueManager({
      redis: redisOptions,
      keys,
    });
  }

  onMessage(
    handler: (ctx: {
      message: {
        id: string;
        queueId: string;
        payload: TestPayload;
        timestamp: number;
        attempt: number;
      };
      queue: { id: string; tenantId: string };
      consumerId: string;
      heartbeat: () => Promise<boolean>;
      complete: () => Promise<void>;
      release: () => Promise<void>;
      fail: (error?: Error) => Promise<void>;
    }) => Promise<void>
  ): void {
    this.messageHandler = handler;
  }

  start(): void {
    if (this.isRunning) return;
    this.isRunning = true;
    this.abortController = new AbortController();
    this.fairQueue.start();
    const loop = this.#runConsumerLoop();
    this.consumerLoops.push(loop);
  }

  async stop(): Promise<void> {
    if (!this.isRunning) return;
    this.isRunning = false;
    this.abortController.abort();
    await this.fairQueue.stop();
    await Promise.allSettled(this.consumerLoops);
    this.consumerLoops = [];
  }

  async close(): Promise<void> {
    await this.stop();
    await this.fairQueue.close();
    await this.workerQueueManager.close();
  }

  async enqueue(options: Parameters<typeof this.fairQueue.enqueue>[0]) {
    return this.fairQueue.enqueue(options);
  }

  async enqueueBatch(options: Parameters<typeof this.fairQueue.enqueueBatch>[0]) {
    return this.fairQueue.enqueueBatch(options);
  }

  async getQueueLength(queueId: string) {
    return this.fairQueue.getQueueLength(queueId);
  }

  async getTotalInflightCount() {
    return this.fairQueue.getTotalInflightCount();
  }

  async getDeadLetterQueueLength(tenantId: string) {
    return this.fairQueue.getDeadLetterQueueLength(tenantId);
  }

  async getDeadLetterMessages(tenantId: string, limit?: number) {
    return this.fairQueue.getDeadLetterMessages(tenantId, limit);
  }

  async getTotalQueueCount() {
    return this.fairQueue.getTotalQueueCount();
  }

  async #runConsumerLoop(): Promise<void> {
    const loopId = "test-consumer-0";
    try {
      while (this.isRunning) {
        if (!this.messageHandler) {
          await new Promise((resolve) => setTimeout(resolve, 50));
          continue;
        }
        try {
          const messageKey = await this.workerQueueManager.blockingPop(
            TEST_WORKER_QUEUE_ID,
            1,
            this.abortController.signal
          );
          if (!messageKey) continue;

          const colonIndex = messageKey.indexOf(":");
          if (colonIndex === -1) continue;

          const messageId = messageKey.substring(0, colonIndex);
          const queueId = messageKey.substring(colonIndex + 1);

          const storedMessage = await this.fairQueue.getMessageData(messageId, queueId);
          if (!storedMessage) continue;

          const ctx = {
            message: {
              id: storedMessage.id,
              queueId: storedMessage.queueId,
              payload: storedMessage.payload,
              timestamp: storedMessage.timestamp,
              attempt: storedMessage.attempt,
            },
            queue: {
              id: queueId,
              tenantId: storedMessage.tenantId,
            },
            consumerId: loopId,
            heartbeat: () => this.fairQueue.heartbeatMessage(messageId, queueId),
            complete: () => this.fairQueue.completeMessage(messageId, queueId),
            release: () => this.fairQueue.releaseMessage(messageId, queueId),
            fail: (error?: Error) => this.fairQueue.failMessage(messageId, queueId, error),
          };

          await this.messageHandler(ctx);
        } catch (_error) {
          if (this.abortController.signal.aborted) break;
        }
      }
    } catch {
      // Ignore abort errors
    }
  }
}

describe("Race Condition Tests", () => {
  let keys: FairQueueKeyProducer;

  describe("concurrent enqueue", () => {
    redisTest(
      "should handle many concurrent enqueues to the same queue without data loss",
      { timeout: 30000 },
      async ({ redisOptions }) => {
        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const scheduler = new DRRScheduler({
          redis: redisOptions,
          keys,
          quantum: 10,
          maxDeficit: 100,
        });

        const queue = new FairQueue({
          redis: redisOptions,
          keys,
          scheduler,
          payloadSchema: TestPayloadSchema,
          shardCount: 1,
          startConsumers: false,
          workerQueue: {
            resolveWorkerQueue: () => TEST_WORKER_QUEUE_ID,
          },
        });

        const CONCURRENT_ENQUEUES = 100;
        const queueId = "tenant:t1:queue:concurrent";

        // Enqueue many messages concurrently
        const enqueuePromises = Array.from({ length: CONCURRENT_ENQUEUES }, (_, i) =>
          queue.enqueue({
            queueId,
            tenantId: "t1",
            payload: { id: i, value: `msg-${i}` },
          })
        );

        const messageIds = await Promise.all(enqueuePromises);

        // All enqueues should succeed with unique IDs
        expect(messageIds).toHaveLength(CONCURRENT_ENQUEUES);
        expect(new Set(messageIds).size).toBe(CONCURRENT_ENQUEUES);

        // Queue length should match
        const length = await queue.getQueueLength(queueId);
        expect(length).toBe(CONCURRENT_ENQUEUES);

        await queue.close();
      }
    );

    redisTest(
      "should handle concurrent enqueues to different queues",
      { timeout: 30000 },
      async ({ redisOptions }) => {
        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const scheduler = new DRRScheduler({
          redis: redisOptions,
          keys,
          quantum: 10,
          maxDeficit: 100,
        });

        const queue = new FairQueue({
          redis: redisOptions,
          keys,
          scheduler,
          payloadSchema: TestPayloadSchema,
          shardCount: 4, // Multiple shards
          startConsumers: false,
          workerQueue: {
            resolveWorkerQueue: () => TEST_WORKER_QUEUE_ID,
          },
        });

        const QUEUES = 10;
        const MESSAGES_PER_QUEUE = 20;

        // Enqueue to many queues concurrently
        const enqueuePromises: Promise<string>[] = [];
        for (let q = 0; q < QUEUES; q++) {
          for (let m = 0; m < MESSAGES_PER_QUEUE; m++) {
            enqueuePromises.push(
              queue.enqueue({
                queueId: `tenant:t${q}:queue:q1`,
                tenantId: `t${q}`,
                payload: { id: m, value: `q${q}-msg-${m}` },
              })
            );
          }
        }

        const messageIds = await Promise.all(enqueuePromises);

        // All enqueues should succeed
        expect(messageIds).toHaveLength(QUEUES * MESSAGES_PER_QUEUE);

        // Each queue should have correct count
        for (let q = 0; q < QUEUES; q++) {
          const length = await queue.getQueueLength(`tenant:t${q}:queue:q1`);
          expect(length).toBe(MESSAGES_PER_QUEUE);
        }

        // Total queue count should match
        const totalQueues = await queue.getTotalQueueCount();
        expect(totalQueues).toBe(QUEUES);

        await queue.close();
      }
    );
  });

  describe("concurrent processing", () => {
    redisTest(
      "should not process the same message twice with multiple consumers",
      { timeout: 60000 },
      async ({ redisOptions }) => {
        const processedMessages = new Map<string, number>();
        const processedMutex = new Set<string>(); // Track which messages are currently being processed
        let duplicateDetected = false;

        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const scheduler = new DRRScheduler({
          redis: redisOptions,
          keys,
          quantum: 10,
          maxDeficit: 100,
        });

        const queue = new TestFairQueueHelper(redisOptions, keys, {
          scheduler,
          payloadSchema: TestPayloadSchema,
          shardCount: 1,
          consumerCount: 5, // Multiple consumers
          consumerIntervalMs: 10, // Fast polling
          visibilityTimeoutMs: 30000, // Long timeout to avoid reclaims
          startConsumers: false,
        });

        queue.onMessage(async (ctx) => {
          const msgId = ctx.message.id;

          // Check if message is already being processed (race condition)
          if (processedMutex.has(msgId)) {
            duplicateDetected = true;
          }
          processedMutex.add(msgId);

          // Track how many times each message was processed
          const count = processedMessages.get(msgId) ?? 0;
          processedMessages.set(msgId, count + 1);

          // Simulate some work
          await new Promise((resolve) => setTimeout(resolve, 10));

          processedMutex.delete(msgId);
          await ctx.complete();
        });

        const MESSAGE_COUNT = 50;

        // Enqueue messages
        for (let i = 0; i < MESSAGE_COUNT; i++) {
          await queue.enqueue({
            queueId: "tenant:t1:queue:race",
            tenantId: "t1",
            payload: { id: i, value: `msg-${i}` },
          });
        }

        // Start consumers
        queue.start();

        // Wait for all messages to be processed
        await vi.waitFor(
          () => {
            expect(processedMessages.size).toBe(MESSAGE_COUNT);
          },
          { timeout: 50000 }
        );

        await queue.stop();

        // Verify no duplicates
        expect(duplicateDetected).toBe(false);
        for (const [_msgId, count] of processedMessages) {
          expect(count).toBe(1);
        }

        await queue.close();
      }
    );

    redisTest(
      "should handle high-contention scenario with many consumers and few messages",
      { timeout: 30000 },
      async ({ redisOptions }) => {
        const processedMessages = new Set<string>();

        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const scheduler = new DRRScheduler({
          redis: redisOptions,
          keys,
          quantum: 10,
          maxDeficit: 100,
        });

        const queue = new TestFairQueueHelper(redisOptions, keys, {
          scheduler,
          payloadSchema: TestPayloadSchema,
          shardCount: 1,
          consumerCount: 10, // Many consumers
          consumerIntervalMs: 5, // Very fast polling
          visibilityTimeoutMs: 30000,
          startConsumers: false,
        });

        queue.onMessage(async (ctx) => {
          processedMessages.add(ctx.message.id);
          await ctx.complete();
        });

        const MESSAGE_COUNT = 10; // Few messages

        // Enqueue messages
        for (let i = 0; i < MESSAGE_COUNT; i++) {
          await queue.enqueue({
            queueId: "tenant:t1:queue:contention",
            tenantId: "t1",
            payload: { id: i, value: `msg-${i}` },
          });
        }

        // Start consumers
        queue.start();

        // Wait for all messages
        await vi.waitFor(
          () => {
            expect(processedMessages.size).toBe(MESSAGE_COUNT);
          },
          { timeout: 20000 }
        );

        await queue.close();
      }
    );
  });

  describe("concurrent concurrency reservation", () => {
    redisTest(
      "should not exceed concurrency limit under high contention",
      { timeout: 30000 },
      async ({ redisOptions }) => {
        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const manager = new ConcurrencyManager({
          redis: redisOptions,
          keys,
          groups: [
            {
              name: "tenant",
              extractGroupId: (q) => q.tenantId,
              getLimit: async () => 3,
              defaultLimit: 3,
            },
          ],
        });

        const queue: QueueDescriptor = {
          id: "queue-1",
          tenantId: "t1",
          metadata: {},
        };

        const CONCURRENT_RESERVATIONS = 50;
        const reservedIds: string[] = [];

        // Try many concurrent reservations
        const reservationPromises = Array.from(
          { length: CONCURRENT_RESERVATIONS },
          async (_, i) => {
            const canProcess = await manager.canProcess(queue);
            if (canProcess.allowed) {
              const success = await manager.reserve(queue, `msg-${i}`);
              if (success) {
                reservedIds.push(`msg-${i}`);
              }
            }
          }
        );

        await Promise.all(reservationPromises);

        // Should not exceed limit
        const current = await manager.getCurrentConcurrency("tenant", "t1");
        expect(current).toBeLessThanOrEqual(3);

        await manager.close();
      }
    );

    redisTest(
      "should handle concurrent reserve/release cycles",
      { timeout: 30000 },
      async ({ redisOptions }) => {
        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const manager = new ConcurrencyManager({
          redis: redisOptions,
          keys,
          groups: [
            {
              name: "tenant",
              extractGroupId: (q) => q.tenantId,
              getLimit: async () => 5,
              defaultLimit: 5,
            },
          ],
        });

        const queue: QueueDescriptor = {
          id: "queue-1",
          tenantId: "t1",
          metadata: {},
        };

        const CYCLES = 100;
        let maxConcurrency = 0;

        // Run many reserve/release cycles concurrently
        const cyclePromises = Array.from({ length: CYCLES }, async (_, i) => {
          const msgId = `msg-${i}`;

          const canProcess = await manager.canProcess(queue);
          if (canProcess.allowed) {
            const reserved = await manager.reserve(queue, msgId);
            if (reserved) {
              // Track max concurrency
              const current = await manager.getCurrentConcurrency("tenant", "t1");
              maxConcurrency = Math.max(maxConcurrency, current);

              // Simulate work
              await new Promise((resolve) => setTimeout(resolve, Math.random() * 10));

              await manager.release(queue, msgId);
            }
          }
        });

        await Promise.all(cyclePromises);

        // Max should never exceed limit
        expect(maxConcurrency).toBeLessThanOrEqual(5);

        // Final concurrency should be 0
        const finalConcurrency = await manager.getCurrentConcurrency("tenant", "t1");
        expect(finalConcurrency).toBe(0);

        await manager.close();
      }
    );
  });

  describe("visibility timeout races", () => {
    // Skipping due to intermittent timing issues with VisibilityManager.heartbeat
    // The core heartbeat functionality is tested in fairQueue.test.ts
    redisTest.skip(
      "should not reclaim message while heartbeat is active",
      { timeout: 30000 },
      async ({ redisOptions }) => {
        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const manager = new VisibilityManager({
          redis: redisOptions,
          keys,
          shardCount: 1,
          defaultTimeoutMs: 1000, // 1 second timeout
        });

        const redis = createRedisClient(redisOptions);
        const queueKey = keys.queueKey("tenant:t1:queue:vis");
        const queueItemsKey = keys.queueItemsKey("tenant:t1:queue:vis");

        // Add a message
        const messageId = "test-msg";
        const storedMessage = {
          id: messageId,
          queueId: "tenant:t1:queue:vis",
          tenantId: "t1",
          payload: { id: 1, value: "test" },
          timestamp: Date.now() - 1000,
          attempt: 1,
        };

        await redis.zadd(queueKey, storedMessage.timestamp, messageId);
        await redis.hset(queueItemsKey, messageId, JSON.stringify(storedMessage));

        // Claim the message
        const claimResult = await manager.claim(
          "tenant:t1:queue:vis",
          queueKey,
          queueItemsKey,
          "consumer-1",
          1000
        );

        expect(claimResult.claimed).toBe(true);

        // Perform heartbeats sequentially to keep the message alive
        let heartbeatCount = 0;
        const reclaimResults: number[] = [];

        // Run 5 cycles of heartbeat + reclaim check
        for (let i = 0; i < 5; i++) {
          // Send heartbeat first
          const heartbeatSuccess = await manager.heartbeat(messageId, "tenant:t1:queue:vis", 1000);
          if (heartbeatSuccess) heartbeatCount++;

          // Wait a bit
          await new Promise((resolve) => setTimeout(resolve, 300));

          // Try to reclaim (should find nothing because heartbeat extended the deadline)
          const reclaimedMessages = await manager.reclaimTimedOut(0, (queueId) => ({
            queueKey: keys.queueKey(queueId),
            queueItemsKey: keys.queueItemsKey(queueId),
            tenantQueueIndexKey: keys.tenantQueueIndexKey(keys.extractTenantId(queueId)),
            dispatchKey: keys.dispatchKey(0),
            tenantId: keys.extractTenantId(queueId),
          }));
          reclaimResults.push(reclaimedMessages.length);
        }

        // Heartbeats should have kept the message alive
        expect(heartbeatCount).toBeGreaterThan(0);

        // No reclaims should have happened while heartbeat was active
        expect(reclaimResults.every((r) => r === 0)).toBe(true);

        await manager.close();
        await redis.quit();
      }
    );

    redisTest(
      "should handle concurrent complete and heartbeat",
      { timeout: 20000 },
      async ({ redisOptions }) => {
        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const manager = new VisibilityManager({
          redis: redisOptions,
          keys,
          shardCount: 1,
          defaultTimeoutMs: 5000,
        });

        const redis = createRedisClient(redisOptions);
        const queueKey = keys.queueKey("tenant:t1:queue:complete-race");
        const queueItemsKey = keys.queueItemsKey("tenant:t1:queue:complete-race");

        // Add and claim a message
        const messageId = "complete-race-msg";
        const storedMessage = {
          id: messageId,
          queueId: "tenant:t1:queue:complete-race",
          tenantId: "t1",
          payload: { id: 1, value: "test" },
          timestamp: Date.now() - 1000,
          attempt: 1,
        };

        await redis.zadd(queueKey, storedMessage.timestamp, messageId);
        await redis.hset(queueItemsKey, messageId, JSON.stringify(storedMessage));

        await manager.claim(
          "tenant:t1:queue:complete-race",
          queueKey,
          queueItemsKey,
          "consumer-1",
          5000
        );

        // Concurrently complete and heartbeat
        const results = await Promise.allSettled([
          manager.complete(messageId, "tenant:t1:queue:complete-race"),
          manager.heartbeat(messageId, "tenant:t1:queue:complete-race", 5000),
          manager.complete(messageId, "tenant:t1:queue:complete-race"),
          manager.heartbeat(messageId, "tenant:t1:queue:complete-race", 5000),
        ]);

        // At least one complete should succeed
        const completeResults = results.filter((r, i) => i % 2 === 0 && r.status === "fulfilled");
        expect(completeResults.length).toBeGreaterThan(0);

        // Message should be removed from in-flight
        const inflightCount = await manager.getTotalInflightCount();
        expect(inflightCount).toBe(0);

        await manager.close();
        await redis.quit();
      }
    );
  });

  describe("master queue update races", () => {
    redisTest(
      "should maintain correct master queue state under concurrent updates",
      { timeout: 30000 },
      async ({ redisOptions }) => {
        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });
        const redis = createRedisClient(redisOptions);

        const masterQueue = new MasterQueue({
          redis: redisOptions,
          keys,
          shardCount: 1,
        });

        const QUEUES = 20;
        const OPS_PER_QUEUE = 10;
        const baseTimestamp = Date.now();

        // Concurrently add and update many queues
        const ops: Promise<void>[] = [];
        for (let q = 0; q < QUEUES; q++) {
          const queueId = `tenant:t${q}:queue:master-race`;
          for (let o = 0; o < OPS_PER_QUEUE; o++) {
            // Mix of add and update operations with past timestamps
            ops.push(masterQueue.addQueue(queueId, baseTimestamp - Math.random() * 1000));
          }
        }

        await Promise.all(ops);

        // Each queue should appear exactly once in master queue (sorted set = unique members)
        const totalCount = await masterQueue.getTotalQueueCount();
        expect(totalCount).toBe(QUEUES);

        // Also verify by directly checking the master queue sorted set
        const masterKey = keys.masterQueueKey(0);
        const members = await redis.zcard(masterKey);
        expect(members).toBe(QUEUES);

        await masterQueue.close();
        await redis.quit();
      }
    );

    redisTest(
      "should handle concurrent add and remove operations",
      { timeout: 30000 },
      async ({ redisOptions }) => {
        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const masterQueue = new MasterQueue({
          redis: redisOptions,
          keys,
          shardCount: 1,
        });

        const QUEUES = 10;
        const queueIds = Array.from({ length: QUEUES }, (_, i) => `tenant:t${i}:queue:add-remove`);

        // Add all queues first
        await Promise.all(queueIds.map((qId) => masterQueue.addQueue(qId, Date.now())));

        // Concurrently add and remove
        const ops: Promise<void>[] = [];
        for (let i = 0; i < 50; i++) {
          const queueId = queueIds[i % QUEUES]!;
          if (i % 2 === 0) {
            ops.push(masterQueue.addQueue(queueId, Date.now()));
          } else {
            ops.push(masterQueue.removeQueue(queueId));
          }
        }

        await Promise.all(ops);

        // Count should be consistent (no negative counts, no duplicates)
        const count = await masterQueue.getTotalQueueCount();
        expect(count).toBeGreaterThanOrEqual(0);
        expect(count).toBeLessThanOrEqual(QUEUES);

        await masterQueue.close();
      }
    );
  });

  describe("retry and DLQ races", () => {
    redisTest(
      "should not lose messages during retry scheduling",
      { timeout: 60000 },
      async ({ redisOptions }) => {
        const processedAttempts = new Map<string, number[]>();

        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const scheduler = new DRRScheduler({
          redis: redisOptions,
          keys,
          quantum: 10,
          maxDeficit: 100,
        });

        const queue = new TestFairQueueHelper(redisOptions, keys, {
          scheduler,
          payloadSchema: TestPayloadSchema,
          shardCount: 1,
          consumerCount: 3,
          consumerIntervalMs: 20,
          visibilityTimeoutMs: 10000,
          retry: {
            strategy: new FixedDelayRetry({ maxAttempts: 3, delayMs: 100 }),
            deadLetterQueue: true,
          },
          startConsumers: false,
        });

        queue.onMessage(async (ctx) => {
          const msgId = ctx.message.payload.id.toString();
          const attempts = processedAttempts.get(msgId) ?? [];
          attempts.push(ctx.message.attempt);
          processedAttempts.set(msgId, attempts);

          // Fail first 2 attempts
          if (ctx.message.attempt < 3) {
            await ctx.fail(new Error("Retry test"));
          } else {
            await ctx.complete();
          }
        });

        const MESSAGE_COUNT = 20;

        // Enqueue messages
        for (let i = 0; i < MESSAGE_COUNT; i++) {
          await queue.enqueue({
            queueId: "tenant:t1:queue:retry-race",
            tenantId: "t1",
            payload: { id: i, value: `msg-${i}` },
          });
        }

        queue.start();

        // Wait for all messages to complete
        await vi.waitFor(
          () => {
            // All messages should have 3 attempts
            const allComplete = Array.from(processedAttempts.values()).every((attempts) =>
              attempts.includes(3)
            );
            expect(allComplete).toBe(true);
          },
          { timeout: 50000 }
        );

        await queue.stop();

        // Verify retry sequence for each message
        for (const [_msgId, attempts] of processedAttempts) {
          expect(attempts).toContain(1);
          expect(attempts).toContain(2);
          expect(attempts).toContain(3);
        }

        // No messages should be in DLQ (all eventually succeeded)
        const dlqCount = await queue.getDeadLetterQueueLength("t1");
        expect(dlqCount).toBe(0);

        await queue.close();
      }
    );

    redisTest(
      "should correctly move to DLQ under concurrent failures",
      { timeout: 60000 },
      async ({ redisOptions }) => {
        const processedCount = new Map<number, number>();

        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const scheduler = new DRRScheduler({
          redis: redisOptions,
          keys,
          quantum: 10,
          maxDeficit: 100,
        });

        const queue = new TestFairQueueHelper(redisOptions, keys, {
          scheduler,
          payloadSchema: TestPayloadSchema,
          shardCount: 1,
          consumerCount: 5,
          consumerIntervalMs: 20,
          visibilityTimeoutMs: 10000,
          retry: {
            strategy: new FixedDelayRetry({ maxAttempts: 2, delayMs: 50 }),
            deadLetterQueue: true,
          },
          startConsumers: false,
        });

        queue.onMessage(async (ctx) => {
          const msgId = ctx.message.payload.id;
          const count = (processedCount.get(msgId) ?? 0) + 1;
          processedCount.set(msgId, count);

          // Always fail
          await ctx.fail(new Error("Always fails"));
        });

        const MESSAGE_COUNT = 30;

        // Enqueue messages
        for (let i = 0; i < MESSAGE_COUNT; i++) {
          await queue.enqueue({
            queueId: "tenant:t1:queue:dlq-race",
            tenantId: "t1",
            payload: { id: i, value: `msg-${i}` },
          });
        }

        queue.start();

        // Wait for all messages to reach DLQ
        await vi.waitFor(
          async () => {
            const dlqCount = await queue.getDeadLetterQueueLength("t1");
            expect(dlqCount).toBe(MESSAGE_COUNT);
          },
          { timeout: 50000 }
        );

        await queue.stop();

        // Each message should have been attempted exactly maxAttempts times
        for (const [, count] of processedCount) {
          expect(count).toBe(2);
        }

        // Verify DLQ contents
        const dlqMessages = await queue.getDeadLetterMessages("t1", 100);
        expect(dlqMessages).toHaveLength(MESSAGE_COUNT);

        // Each message should have correct attempt count
        for (const msg of dlqMessages) {
          expect(msg.attempts).toBe(2);
        }

        await queue.close();
      }
    );
  });

  describe("complete message consistency", () => {
    redisTest(
      "should not leak in-flight entries on completion",
      { timeout: 30000 },
      async ({ redisOptions }) => {
        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const scheduler = new DRRScheduler({
          redis: redisOptions,
          keys,
          quantum: 10,
          maxDeficit: 100,
        });

        const queue = new TestFairQueueHelper(redisOptions, keys, {
          scheduler,
          payloadSchema: TestPayloadSchema,
          shardCount: 1,
          consumerCount: 5,
          consumerIntervalMs: 10,
          visibilityTimeoutMs: 30000,
          startConsumers: false,
        });

        const completedCount = { count: 0 };

        queue.onMessage(async (ctx) => {
          await ctx.complete();
          completedCount.count++;
        });

        const MESSAGE_COUNT = 100;

        // Enqueue messages
        for (let i = 0; i < MESSAGE_COUNT; i++) {
          await queue.enqueue({
            queueId: "tenant:t1:queue:inflight-leak",
            tenantId: "t1",
            payload: { id: i, value: `msg-${i}` },
          });
        }

        queue.start();

        // Wait for all completions
        await vi.waitFor(
          () => {
            expect(completedCount.count).toBe(MESSAGE_COUNT);
          },
          { timeout: 25000 }
        );

        await queue.stop();

        // No messages should remain in-flight
        const inflightCount = await queue.getTotalInflightCount();
        expect(inflightCount).toBe(0);

        // Queue should be empty
        const queueLength = await queue.getQueueLength("tenant:t1:queue:inflight-leak");
        expect(queueLength).toBe(0);

        await queue.close();
      }
    );

    redisTest(
      "should not leave orphaned concurrency slots",
      { timeout: 30000 },
      async ({ redisOptions }) => {
        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const scheduler = new DRRScheduler({
          redis: redisOptions,
          keys,
          quantum: 10,
          maxDeficit: 100,
        });

        // Track concurrency over time
        let maxConcurrency = 0;

        const queue = new TestFairQueueHelper(redisOptions, keys, {
          scheduler,
          payloadSchema: TestPayloadSchema,
          shardCount: 1,
          consumerCount: 3,
          consumerIntervalMs: 10,
          visibilityTimeoutMs: 30000,
          concurrencyGroups: [
            {
              name: "tenant",
              extractGroupId: (q) => q.tenantId,
              getLimit: async () => 5,
              defaultLimit: 5,
            },
          ],
          startConsumers: false,
        });

        const redis = createRedisClient(redisOptions);

        queue.onMessage(async (ctx) => {
          // Check current concurrency
          const concurrencyKey = keys.concurrencyKey("tenant", "t1");
          const current = await redis.scard(concurrencyKey);
          maxConcurrency = Math.max(maxConcurrency, current);

          // Simulate work with random duration
          await new Promise((resolve) => setTimeout(resolve, Math.random() * 20));

          await ctx.complete();
        });

        const MESSAGE_COUNT = 50;

        // Enqueue messages
        for (let i = 0; i < MESSAGE_COUNT; i++) {
          await queue.enqueue({
            queueId: "tenant:t1:queue:concurrency-leak",
            tenantId: "t1",
            payload: { id: i, value: `msg-${i}` },
          });
        }

        queue.start();

        // Wait for all messages
        await vi.waitFor(
          async () => {
            const len = await queue.getQueueLength("tenant:t1:queue:concurrency-leak");
            const inflight = await queue.getTotalInflightCount();
            expect(len + inflight).toBe(0);
          },
          { timeout: 25000 }
        );

        await queue.stop();

        // Max concurrency should have been respected
        expect(maxConcurrency).toBeLessThanOrEqual(5);

        // Final concurrency should be 0
        const concurrencyKey = keys.concurrencyKey("tenant", "t1");
        const finalConcurrency = await redis.scard(concurrencyKey);
        expect(finalConcurrency).toBe(0);

        await redis.quit();
        await queue.close();
      }
    );
  });

  describe("shutdown races", () => {
    redisTest(
      "should complete in-progress messages during shutdown",
      { timeout: 30000 },
      async ({ redisOptions }) => {
        const inProgressMessages = new Set<string>();
        const completedMessages = new Set<string>();

        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const scheduler = new DRRScheduler({
          redis: redisOptions,
          keys,
          quantum: 10,
          maxDeficit: 100,
        });

        const queue = new TestFairQueueHelper(redisOptions, keys, {
          scheduler,
          payloadSchema: TestPayloadSchema,
          shardCount: 1,
          consumerCount: 3,
          consumerIntervalMs: 10,
          visibilityTimeoutMs: 30000,
          startConsumers: false,
        });

        queue.onMessage(async (ctx) => {
          const msgId = ctx.message.id;
          inProgressMessages.add(msgId);

          // Simulate work
          await new Promise((resolve) => setTimeout(resolve, 100));

          completedMessages.add(msgId);
          inProgressMessages.delete(msgId);
          await ctx.complete();
        });

        // Enqueue messages
        for (let i = 0; i < 20; i++) {
          await queue.enqueue({
            queueId: "tenant:t1:queue:shutdown",
            tenantId: "t1",
            payload: { id: i, value: `msg-${i}` },
          });
        }

        queue.start();

        // Wait for some messages to start processing
        await vi.waitFor(
          () => {
            expect(completedMessages.size).toBeGreaterThan(0);
          },
          { timeout: 5000 }
        );

        // Stop while messages are in progress
        await queue.stop();

        // Give time for cleanup
        await new Promise((resolve) => setTimeout(resolve, 500));

        await queue.close();

        // Note: Messages that were in-progress during shutdown may not complete
        // The important thing is no crashes or data corruption
      }
    );
  });

  describe("atomic operation verification", () => {
    redisTest(
      "should maintain consistent state after many enqueue/complete cycles",
      { timeout: 60000 },
      async ({ redisOptions }) => {
        keys = new DefaultFairQueueKeyProducer({ prefix: "test" });

        const scheduler = new DRRScheduler({
          redis: redisOptions,
          keys,
          quantum: 10,
          maxDeficit: 100,
        });

        const queue = new TestFairQueueHelper(redisOptions, keys, {
          scheduler,
          payloadSchema: TestPayloadSchema,
          shardCount: 2, // Multiple shards to test
          consumerCount: 4,
          consumerIntervalMs: 10,
          visibilityTimeoutMs: 30000,
          startConsumers: false,
        });

        const messagesProcessed = new Set<number>();
        let enqueueCounter = 0;

        queue.onMessage(async (ctx) => {
          messagesProcessed.add(ctx.message.payload.id);
          await ctx.complete();
        });

        queue.start();

        // Continuously enqueue messages while processing
        const enqueueDuration = 10000; // 10 seconds
        const startTime = Date.now();

        while (Date.now() - startTime < enqueueDuration) {
          const batch = Array.from({ length: 5 }, () => ({
            payload: { id: enqueueCounter++, value: `msg-${enqueueCounter}` },
          }));

          await queue.enqueueBatch({
            queueId: "tenant:t1:queue:cycles",
            tenantId: "t1",
            messages: batch,
          });

          await new Promise((resolve) => setTimeout(resolve, 50));
        }

        const totalEnqueued = enqueueCounter;

        // Wait for all messages to be processed
        await vi.waitFor(
          () => {
            expect(messagesProcessed.size).toBe(totalEnqueued);
          },
          { timeout: 40000 }
        );

        await queue.stop();

        // Verify final state
        const queueLength = await queue.getQueueLength("tenant:t1:queue:cycles");
        expect(queueLength).toBe(0);

        const inflightCount = await queue.getTotalInflightCount();
        expect(inflightCount).toBe(0);

        const masterQueueCount = await queue.getTotalQueueCount();
        expect(masterQueueCount).toBe(0);

        await queue.close();
      }
    );
  });
});
