import { describe, expect, it, vi, beforeEach } from "vitest";
import { ErrorCode } from "@pocketcoder/shared/constants";
import { MessageRouter } from "../message-router.js";
// Suppress logger output in tests
vi.mock("../../utils/logger.js", () => ({
    logger: {
        debug: vi.fn(),
        info: vi.fn(),
        warn: vi.fn(),
        error: vi.fn(),
    },
}));
const { logger } = await import("../../utils/logger.js");
const mockLogger = vi.mocked(logger);
/** Create a minimal mock GatewayClient with on/off/send */
function createMockClient() {
    const listeners = new Map();
    const client = {
        on: vi.fn((event, listener) => {
            let set = listeners.get(event);
            if (!set) {
                set = new Set();
                listeners.set(event, set);
            }
            set.add(listener);
            return client;
        }),
        off: vi.fn((event, listener) => {
            const set = listeners.get(event);
            if (set) {
                set.delete(listener);
            }
            return client;
        }),
        send: vi.fn(),
        triggerMessage(msg) {
            const set = listeners.get("message");
            if (set) {
                for (const listener of set) {
                    listener(msg);
                }
            }
        },
    };
    return client;
}
function createFsListRequest(requestId = "req-1", sessionId = "proj-1") {
    return {
        type: "fs_list",
        requestId,
        sessionId,
        payload: { path: "" },
    };
}
describe("MessageRouter", () => {
    let router;
    let client;
    beforeEach(() => {
        vi.clearAllMocks();
        router = new MessageRouter();
        client = createMockClient();
    });
    describe("handler registration", () => {
        it("should register a handler for a message type", () => {
            const handler = vi.fn(async () => { });
            router.registerHandler("fs_list", handler);
            router.attach(client);
            client.triggerMessage(createFsListRequest());
            // Allow the async handler to execute
            expect(handler).toHaveBeenCalledOnce();
        });
        it("should remove a handler for a message type", async () => {
            const handler = vi.fn(async () => { });
            router.registerHandler("fs_list", handler);
            router.removeHandler("fs_list");
            router.attach(client);
            client.triggerMessage(createFsListRequest());
            // Handler should not be called since it was removed
            expect(handler).not.toHaveBeenCalled();
        });
    });
    describe("message routing", () => {
        it("should route message to correct handler", async () => {
            const fsListHandler = vi.fn(async () => { });
            const gitStatusHandler = vi.fn(async () => { });
            router.registerHandler("fs_list", fsListHandler);
            router.registerHandler("git_status", gitStatusHandler);
            router.attach(client);
            const msg = createFsListRequest();
            client.triggerMessage(msg);
            expect(fsListHandler).toHaveBeenCalledOnce();
            expect(gitStatusHandler).not.toHaveBeenCalled();
            const [receivedMsg, sendFn] = vi.mocked(fsListHandler).mock.calls[0];
            expect(receivedMsg).toEqual(msg);
            expect(typeof sendFn).toBe("function");
        });
        it("should pass a working send function to handler", async () => {
            const handler = vi.fn(async (_msg, send) => {
                send({
                    type: "fs_list_response",
                    requestId: "req-1",
                    sessionId: "session-1",
                    payload: { path: "", children: [], seq: 1 },
                });
            });
            router.registerHandler("fs_list", handler);
            router.attach(client);
            client.triggerMessage(createFsListRequest());
            // Wait for async handler
            await vi.waitFor(() => {
                expect(vi.mocked(client.send)).toHaveBeenCalledOnce();
            });
            const sent = vi.mocked(client.send).mock.calls[0][0];
            expect(sent.type).toBe("fs_list_response");
        });
        it("should ignore ping messages", () => {
            const handler = vi.fn(async () => { });
            router.registerHandler("fs_list", handler);
            router.attach(client);
            const pingMsg = { type: "ping" };
            client.triggerMessage(pingMsg);
            expect(handler).not.toHaveBeenCalled();
            expect(mockLogger.warn).not.toHaveBeenCalled();
        });
        it("should ignore response messages", () => {
            router.attach(client);
            client.triggerMessage({
                type: "fs_list_response",
                requestId: "req-1",
                sessionId: "session-1",
                payload: { path: "", children: [], seq: 1 },
            });
            expect(mockLogger.warn).not.toHaveBeenCalled();
        });
        it("should ignore error response messages", () => {
            router.attach(client);
            client.triggerMessage({
                type: "error",
                requestId: "req-1",
                error: { code: "UNKNOWN_ERROR", message: "test" },
            });
            expect(mockLogger.warn).not.toHaveBeenCalled();
        });
    });
    describe("unknown message types", () => {
        it("should log warning for unknown message type", () => {
            router.attach(client);
            client.triggerMessage({
                type: "some_unknown_type",
                requestId: "req-1",
            });
            expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining("Unknown message type: some_unknown_type"));
        });
        it("should send error response for unknown type with requestId", () => {
            router.attach(client);
            client.triggerMessage({
                type: "some_unknown_type",
                requestId: "req-42",
            });
            expect(vi.mocked(client.send)).toHaveBeenCalledOnce();
            const sent = vi.mocked(client.send).mock.calls[0][0];
            expect(sent.type).toBe("error");
            expect(sent.requestId).toBe("req-42");
            expect(sent.error.code).toBe(ErrorCode.INVALID_REQUEST);
        });
    });
    describe("error handling", () => {
        it("should not crash on handler errors", async () => {
            const handler = vi.fn(async () => {
                throw new Error("Handler exploded");
            });
            router.registerHandler("fs_list", handler);
            router.attach(client);
            // Should not throw
            client.triggerMessage(createFsListRequest());
            // Wait for the async error handler to run
            await vi.waitFor(() => {
                expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Handler error for fs_list: Handler exploded"));
            });
        });
        it("should send error response when handler throws", async () => {
            const handler = vi.fn(async () => {
                throw new Error("Something went wrong");
            });
            router.registerHandler("fs_list", handler);
            router.attach(client);
            client.triggerMessage(createFsListRequest());
            await vi.waitFor(() => {
                expect(vi.mocked(client.send)).toHaveBeenCalledOnce();
            });
            const sent = vi.mocked(client.send).mock.calls[0][0];
            expect(sent.type).toBe("error");
            expect(sent.requestId).toBe("req-1");
            expect(sent.error.code).toBe(ErrorCode.UNKNOWN_ERROR);
            expect(sent.error.message).toBe("Something went wrong");
        });
        it("should handle non-Error thrown values", async () => {
            const handler = vi.fn(async () => {
                throw "string error";
            });
            router.registerHandler("fs_list", handler);
            router.attach(client);
            client.triggerMessage(createFsListRequest());
            await vi.waitFor(() => {
                expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("string error"));
            });
        });
    });
    describe("attach/detach", () => {
        it("should stop routing after detach", () => {
            const handler = vi.fn(async () => { });
            router.registerHandler("fs_list", handler);
            router.attach(client);
            router.detach();
            client.triggerMessage(createFsListRequest());
            expect(handler).not.toHaveBeenCalled();
        });
        it("should call off when detaching", () => {
            router.attach(client);
            router.detach();
            expect(vi.mocked(client.off)).toHaveBeenCalledWith("message", expect.any(Function));
        });
    });
});
//# sourceMappingURL=message-router.test.js.map