import { afterEach, describe, expect, it, vi } from "vitest";
import { initQueue, enqueueMessage, startDrain, isProcessing, getQueueSize, clearQueue, removeSession, clearAllQueues, } from "../message-queue.js";
vi.mock("../../utils/logger.js", () => ({
    logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
}));
function makeMessage(agentSessionId, content = "test", requestId = "req-1") {
    return {
        projectPath: "/tmp/project",
        content,
        sessionId: "session-1",
        requestId,
        agentSessionId,
    };
}
describe("message-queue", () => {
    afterEach(() => {
        clearAllQueues();
    });
    describe("initQueue", () => {
        it("should initialize a queue for a session", () => {
            const processFn = vi.fn();
            initQueue("session-a", processFn);
            expect(getQueueSize("session-a")).toBe(0);
            expect(isProcessing("session-a")).toBe(false);
        });
        it("should not overwrite existing queue", () => {
            const processFn1 = vi.fn();
            const processFn2 = vi.fn();
            initQueue("session-a", processFn1);
            enqueueMessage(makeMessage("session-a", "msg1"));
            initQueue("session-a", processFn2);
            // Queue should still have the message
            expect(getQueueSize("session-a")).toBe(1);
        });
    });
    describe("enqueueMessage", () => {
        it("should return 'started' for first message when not processing", () => {
            initQueue("session-a", vi.fn());
            const result = enqueueMessage(makeMessage("session-a"));
            expect(result).toBe("started");
        });
        it("should queue requests per project/session", () => {
            initQueue("session-a", vi.fn());
            initQueue("session-b", vi.fn());
            enqueueMessage(makeMessage("session-a", "msg1", "r1"));
            enqueueMessage(makeMessage("session-a", "msg2", "r2"));
            enqueueMessage(makeMessage("session-b", "msg3", "r3"));
            expect(getQueueSize("session-a")).toBe(2);
            expect(getQueueSize("session-b")).toBe(1);
        });
        it("should reject when queue full (50 messages)", () => {
            initQueue("session-a", vi.fn());
            // Fill the queue to max
            for (let i = 0; i < 50; i++) {
                enqueueMessage(makeMessage("session-a", `msg-${i}`, `req-${i}`));
            }
            expect(getQueueSize("session-a")).toBe(50);
            // 51st message should be rejected
            const result = enqueueMessage(makeMessage("session-a", "overflow", "req-overflow"));
            expect(result).toBe("full");
            expect(getQueueSize("session-a")).toBe(50);
        });
        it("should return 'full' for uninitialized session", () => {
            const result = enqueueMessage(makeMessage("unknown-session"));
            expect(result).toBe("full");
        });
    });
    describe("startDrain / sequential processing", () => {
        it("should process messages in FIFO order", async () => {
            const processed = [];
            const processFn = async (msg) => {
                processed.push(msg.content);
            };
            initQueue("session-a", processFn);
            enqueueMessage(makeMessage("session-a", "first", "r1"));
            enqueueMessage(makeMessage("session-a", "second", "r2"));
            enqueueMessage(makeMessage("session-a", "third", "r3"));
            startDrain("session-a");
            // Wait for async drain to complete
            await vi.waitFor(() => {
                expect(processed).toEqual(["first", "second", "third"]);
            });
        });
        it("should process sequentially (one at a time)", async () => {
            let concurrency = 0;
            let maxConcurrency = 0;
            const processFn = async () => {
                concurrency++;
                maxConcurrency = Math.max(maxConcurrency, concurrency);
                // Simulate async work
                await new Promise((resolve) => setTimeout(resolve, 10));
                concurrency--;
            };
            initQueue("session-a", processFn);
            enqueueMessage(makeMessage("session-a", "msg1", "r1"));
            enqueueMessage(makeMessage("session-a", "msg2", "r2"));
            enqueueMessage(makeMessage("session-a", "msg3", "r3"));
            startDrain("session-a");
            await vi.waitFor(() => {
                expect(isProcessing("session-a")).toBe(false);
            }, { timeout: 1000 });
            expect(maxConcurrency).toBe(1);
            expect(getQueueSize("session-a")).toBe(0);
        });
        it("should mark isProcessing during drain", async () => {
            let resolveProcess;
            const processFn = async () => {
                await new Promise((resolve) => {
                    resolveProcess = resolve;
                });
            };
            initQueue("session-a", processFn);
            enqueueMessage(makeMessage("session-a"));
            expect(isProcessing("session-a")).toBe(false);
            startDrain("session-a");
            // Give the drain a tick to start
            await new Promise((resolve) => setTimeout(resolve, 0));
            expect(isProcessing("session-a")).toBe(true);
            // Complete the processing
            resolveProcess();
            await vi.waitFor(() => {
                expect(isProcessing("session-a")).toBe(false);
            });
        });
        it("should return 'queued' when already processing", async () => {
            const resolvers = [];
            const processFn = async () => {
                await new Promise((resolve) => {
                    resolvers.push(resolve);
                });
            };
            initQueue("session-a", processFn);
            enqueueMessage(makeMessage("session-a", "first", "r1"));
            startDrain("session-a");
            // Wait for processing to start
            await new Promise((resolve) => setTimeout(resolve, 0));
            // Second message should be queued
            const result = enqueueMessage(makeMessage("session-a", "second", "r2"));
            expect(result).toBe("queued");
            // Resolve all pending processors to cleanup
            for (const resolve of resolvers)
                resolve();
            // Give drain a moment to pick up the second message
            await new Promise((resolve) => setTimeout(resolve, 0));
            for (const resolve of resolvers)
                resolve();
            await vi.waitFor(() => {
                expect(isProcessing("session-a")).toBe(false);
            });
        });
        it("should handle processing errors without stopping drain", async () => {
            const processed = [];
            const processFn = async (msg) => {
                if (msg.content === "error") {
                    throw new Error("processing error");
                }
                processed.push(msg.content);
            };
            initQueue("session-a", processFn);
            enqueueMessage(makeMessage("session-a", "first", "r1"));
            enqueueMessage(makeMessage("session-a", "error", "r2"));
            enqueueMessage(makeMessage("session-a", "third", "r3"));
            startDrain("session-a");
            await vi.waitFor(() => {
                expect(isProcessing("session-a")).toBe(false);
            });
            // first and third processed, error skipped
            expect(processed).toEqual(["first", "third"]);
        });
    });
    describe("clearQueue", () => {
        it("should clear pending messages", () => {
            initQueue("session-a", vi.fn());
            enqueueMessage(makeMessage("session-a", "msg1", "r1"));
            enqueueMessage(makeMessage("session-a", "msg2", "r2"));
            expect(getQueueSize("session-a")).toBe(2);
            clearQueue("session-a");
            expect(getQueueSize("session-a")).toBe(0);
        });
    });
    describe("removeSession", () => {
        it("should remove all state for a session", () => {
            initQueue("session-a", vi.fn());
            enqueueMessage(makeMessage("session-a"));
            expect(getQueueSize("session-a")).toBe(1);
            removeSession("session-a");
            expect(getQueueSize("session-a")).toBe(0);
            expect(isProcessing("session-a")).toBe(false);
        });
    });
    describe("clearAllQueues", () => {
        it("should clear all session queues", () => {
            initQueue("session-a", vi.fn());
            initQueue("session-b", vi.fn());
            enqueueMessage(makeMessage("session-a"));
            enqueueMessage(makeMessage("session-b"));
            clearAllQueues();
            expect(getQueueSize("session-a")).toBe(0);
            expect(getQueueSize("session-b")).toBe(0);
        });
    });
});
//# sourceMappingURL=message-queue.test.js.map