import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { WebSocketServer } from "ws";
import { GatewayClient } from "../websocket-client.js";
/**
 * Helper: create a local WebSocket server on a random port for testing.
 * Returns the server and its URL.
 */
async function createTestServer() {
    const wss = new WebSocketServer({ port: 0, host: "127.0.0.1" });
    await new Promise((resolve) => {
        wss.on("listening", () => resolve());
    });
    const { port } = wss.address();
    return { wss, url: `ws://127.0.0.1:${port}`, port };
}
/** Helper: wait for the client to reach a specific state */
function waitForState(client, state, timeoutMs = 5000) {
    return new Promise((resolve, reject) => {
        const timer = setTimeout(() => reject(new Error(`Timed out waiting for state: ${state}`)), timeoutMs);
        if (client.state === state) {
            clearTimeout(timer);
            resolve();
            return;
        }
        const event = state === "connected" ? "connected" : "disconnected";
        const handler = () => {
            clearTimeout(timer);
            client.off(event, handler);
            resolve();
        };
        client.on(event, handler);
    });
}
describe("GatewayClient", () => {
    let wss;
    let url;
    let client;
    beforeEach(async () => {
        const server = await createTestServer();
        wss = server.wss;
        url = server.url;
        client = new GatewayClient({ url });
    });
    afterEach(async () => {
        client.disconnect();
        await new Promise((resolve) => {
            wss.close(() => resolve());
        });
    });
    it("should connect to WebSocket server", async () => {
        client.connect();
        await waitForState(client, "connected");
        expect(client.state).toBe("connected");
        expect(client.connected).toBe(true);
    });
    it("should send JSON messages", async () => {
        const received = new Promise((resolve) => {
            wss.on("connection", (ws) => {
                ws.on("message", (data) => {
                    resolve(data.toString());
                });
            });
        });
        client.connect();
        await waitForState(client, "connected");
        const message = {
            type: "daemon_register",
            payload: {
                machineKey: "test-key",
                token: "test-token",
                machine: {
                    os: "linux",
                    osVersion: "Ubuntu 22.04",
                    hostname: "test-host",
                    username: "test-user",
                    label: null,
                },
                projects: [],
            },
        };
        client.send(message);
        const data = await received;
        expect(JSON.parse(data)).toEqual(message);
    });
    it("should receive and parse messages", async () => {
        const messageReceived = new Promise((resolve) => {
            client.on("message", (msg) => {
                if (msg.type !== "ping") {
                    resolve(msg);
                }
            });
        });
        wss.on("connection", (ws) => {
            const response = {
                type: "machines_list_response",
                requestId: "req-1",
                payload: { machines: [] },
            };
            ws.send(JSON.stringify(response));
        });
        client.connect();
        await waitForState(client, "connected");
        const msg = await messageReceived;
        expect(msg.type).toBe("machines_list_response");
        expect(msg.requestId).toBe("req-1");
    });
    it("should handle disconnection", async () => {
        const disconnected = new Promise((resolve) => {
            client.on("disconnected", (code, reason) => {
                resolve({ code, reason });
            });
        });
        wss.on("connection", (ws) => {
            // Close from server side after brief delay
            setTimeout(() => ws.close(1000, "server closing"), 50);
        });
        client.connect();
        await waitForState(client, "connected");
        const result = await disconnected;
        expect(result.code).toBe(1000);
        expect(client.state).toBe("disconnected");
        expect(client.connected).toBe(false);
    });
    it("should handle connection errors", async () => {
        // Close the server before connecting so the connection fails
        await new Promise((resolve) => {
            wss.close(() => resolve());
        });
        const errorReceived = new Promise((resolve) => {
            client.on("error", (err) => {
                resolve(err);
            });
        });
        client.connect();
        const err = await errorReceived;
        expect(err).toBeInstanceOf(Error);
    });
    it("should auto-respond to ping with pong", async () => {
        const pongReceived = new Promise((resolve) => {
            wss.on("connection", (ws) => {
                ws.on("message", (data) => {
                    const msg = JSON.parse(data.toString());
                    if (msg.type === "pong") {
                        resolve(msg.type);
                    }
                });
                // Send ping after connection
                setTimeout(() => ws.send(JSON.stringify({ type: "ping" })), 50);
            });
        });
        client.connect();
        await waitForState(client, "connected");
        const type = await pongReceived;
        expect(type).toBe("pong");
    });
    it("should emit message event for ping", async () => {
        const pingReceived = new Promise((resolve) => {
            client.on("message", (msg) => {
                if (msg.type === "ping") {
                    resolve(msg);
                }
            });
        });
        wss.on("connection", (ws) => {
            setTimeout(() => ws.send(JSON.stringify({ type: "ping" })), 50);
        });
        client.connect();
        await waitForState(client, "connected");
        const msg = await pingReceived;
        expect(msg.type).toBe("ping");
    });
    it("should not connect if already connected", async () => {
        client.connect();
        await waitForState(client, "connected");
        // Second connect should be a no-op
        client.connect();
        expect(client.state).toBe("connected");
    });
    it("should throw when sending on disconnected client", () => {
        const message = {
            type: "daemon_register",
            payload: {
                machineKey: "k",
                token: "t",
                machine: {
                    os: "linux",
                    osVersion: "",
                    hostname: "",
                    username: "",
                    label: null,
                },
                projects: [],
            },
        };
        expect(() => client.send(message)).toThrow("WebSocket is not connected");
    });
    it("should disconnect gracefully", async () => {
        client.connect();
        await waitForState(client, "connected");
        client.disconnect();
        expect(client.state).toBe("disconnected");
        expect(client.connected).toBe(false);
    });
    it("should handle disconnect when already disconnected", () => {
        // Should not throw
        client.disconnect();
        expect(client.state).toBe("disconnected");
    });
    describe("ping()", () => {
        it("should return true when server responds to ping", async () => {
            wss.on("connection", (ws) => {
                // ws library auto-responds to WebSocket protocol pings with pongs
                ws.on("ping", () => {
                    ws.pong();
                });
            });
            client.connect();
            await waitForState(client, "connected");
            const result = await client.ping(3000);
            expect(result).toBe(true);
        });
        it("should return false when not connected", async () => {
            const result = await client.ping(1000);
            expect(result).toBe(false);
        });
        it("should return false when ping times out", async () => {
            let serverWs = null;
            wss.on("connection", (ws) => {
                serverWs = ws;
            });
            client.connect();
            await waitForState(client, "connected");
            // Simulate network partition - terminate the server socket without close handshake
            // but before the ping can complete
            serverWs.terminate();
            // Give the client a moment to not know about the termination
            const result = await client.ping(100); // Short timeout
            expect(result).toBe(false);
        });
        it("should return false after disconnect", async () => {
            client.connect();
            await waitForState(client, "connected");
            client.disconnect();
            const result = await client.ping(1000);
            expect(result).toBe(false);
        });
    });
});
//# sourceMappingURL=websocket-client.test.js.map