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.
 */
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 a specific event on the client */
function waitForEvent(client, event, timeoutMs = 5000) {
    return new Promise((resolve, reject) => {
        const timer = setTimeout(() => reject(new Error(`Timed out waiting for event: ${event}`)), timeoutMs);
        const handler = () => {
            clearTimeout(timer);
            client.off(event, handler);
            resolve();
        };
        client.on(event, handler);
    });
}
/** Helper: wait for a reconnecting event and capture its params */
function waitForReconnecting(client, timeoutMs = 5000) {
    return new Promise((resolve, reject) => {
        const timer = setTimeout(() => reject(new Error("Timed out waiting for reconnecting event")), timeoutMs);
        const handler = (attempt, delayMs) => {
            clearTimeout(timer);
            client.off("reconnecting", handler);
            resolve({ attempt, delayMs });
        };
        client.on("reconnecting", handler);
    });
}
/** Helper: small delay */
function delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
/** Force-close all server connections by terminating sockets */
function terminateAllClients(wss) {
    for (const ws of wss.clients) {
        ws.terminate();
    }
}
describe("GatewayClient reconnection", () => {
    let wss;
    let url;
    let client;
    beforeEach(async () => {
        const server = await createTestServer();
        wss = server.wss;
        url = server.url;
    });
    afterEach(async () => {
        if (client) {
            client.disconnect();
        }
        await new Promise((resolve) => {
            wss.close(() => resolve());
        }).catch(() => {
            // Server may already be closed
        });
    });
    it("should reconnect after unexpected disconnect", async () => {
        client = new GatewayClient({
            url,
            autoReconnect: true,
            reconnectBaseMs: 50,
        });
        client.connect();
        await waitForEvent(client, "connected");
        expect(client.state).toBe("connected");
        // Server terminates the connection (simulates unexpected disconnect)
        const reconnectedPromise = waitForEvent(client, "reconnected");
        terminateAllClients(wss);
        await reconnectedPromise;
        expect(client.state).toBe("connected");
    });
    it("should use exponential backoff", async () => {
        client = new GatewayClient({
            url,
            autoReconnect: true,
            reconnectBaseMs: 50,
            reconnectMaxMs: 60000,
        });
        client.connect();
        await waitForEvent(client, "connected");
        const delays = [];
        const reconnectingPromise = new Promise((resolve) => {
            client.on("reconnecting", (_attempt, delayMs) => {
                delays.push(delayMs);
                if (delays.length >= 3) {
                    resolve();
                }
            });
        });
        // Terminate connections, then close server so reconnect attempts fail
        terminateAllClients(wss);
        await new Promise((resolve) => {
            wss.close(() => resolve());
        });
        // Wait for at least 3 reconnection attempts
        await reconnectingPromise;
        expect(delays[0]).toBe(50); // 50 * 2^0
        expect(delays[1]).toBe(100); // 50 * 2^1
        expect(delays[2]).toBe(200); // 50 * 2^2
    }, 15000);
    it("should cap backoff at max delay", async () => {
        client = new GatewayClient({
            url,
            autoReconnect: true,
            reconnectBaseMs: 50,
            reconnectMaxMs: 200,
        });
        client.connect();
        await waitForEvent(client, "connected");
        const delays = [];
        const reconnectingPromise = new Promise((resolve) => {
            client.on("reconnecting", (_attempt, delayMs) => {
                delays.push(delayMs);
                if (delays.length >= 5) {
                    resolve();
                }
            });
        });
        // Terminate connections, then close server so reconnect attempts fail
        terminateAllClients(wss);
        await new Promise((resolve) => {
            wss.close(() => resolve());
        });
        await reconnectingPromise;
        expect(delays[0]).toBe(50); // 50 * 2^0
        expect(delays[1]).toBe(100); // 50 * 2^1
        expect(delays[2]).toBe(200); // 50 * 2^2 = 200, capped at 200
        expect(delays[3]).toBe(200); // capped
        expect(delays[4]).toBe(200); // capped
        for (const d of delays) {
            expect(d).toBeLessThanOrEqual(200);
        }
    }, 15000);
    it("should reset backoff on successful connection", async () => {
        client = new GatewayClient({
            url,
            autoReconnect: true,
            reconnectBaseMs: 50,
        });
        client.connect();
        await waitForEvent(client, "connected");
        // First disconnect & reconnect cycle
        const reconnectedPromise1 = waitForEvent(client, "reconnected");
        terminateAllClients(wss);
        await reconnectedPromise1;
        expect(client.state).toBe("connected");
        // Second disconnect - should start from attempt 1 again
        const reconnectingPromise = waitForReconnecting(client);
        terminateAllClients(wss);
        const event = await reconnectingPromise;
        expect(event.attempt).toBe(1); // Reset to 1
        expect(event.delayMs).toBe(50); // Base delay
    });
    it("should not reconnect after manual disconnect", async () => {
        client = new GatewayClient({
            url,
            autoReconnect: true,
            reconnectBaseMs: 50,
        });
        client.connect();
        await waitForEvent(client, "connected");
        let reconnectingCalled = false;
        client.on("reconnecting", () => {
            reconnectingCalled = true;
        });
        // Manual disconnect
        client.disconnect();
        await delay(200);
        expect(reconnectingCalled).toBe(false);
        expect(client.state).toBe("disconnected");
    });
    it("should not reconnect when autoReconnect is false", async () => {
        client = new GatewayClient({
            url,
            autoReconnect: false,
        });
        client.connect();
        await waitForEvent(client, "connected");
        let reconnectingCalled = false;
        client.on("reconnecting", () => {
            reconnectingCalled = true;
        });
        // Server terminates connection
        terminateAllClients(wss);
        await delay(200);
        expect(reconnectingCalled).toBe(false);
        expect(client.state).toBe("disconnected");
    });
    it("should emit reconnecting and reconnected events", async () => {
        client = new GatewayClient({
            url,
            autoReconnect: true,
            reconnectBaseMs: 50,
        });
        client.connect();
        await waitForEvent(client, "connected");
        const events = [];
        client.on("reconnecting", () => events.push("reconnecting"));
        client.on("reconnected", () => events.push("reconnected"));
        const reconnectedPromise = waitForEvent(client, "reconnected");
        terminateAllClients(wss);
        await reconnectedPromise;
        expect(events).toEqual(["reconnecting", "reconnected"]);
    });
    it("should cancel pending reconnect on disconnect()", async () => {
        client = new GatewayClient({
            url,
            autoReconnect: true,
            reconnectBaseMs: 2000,
        });
        client.connect();
        await waitForEvent(client, "connected");
        // Trigger reconnection
        const reconnectingPromise = waitForReconnecting(client);
        terminateAllClients(wss);
        await reconnectingPromise;
        expect(client.state).toBe("reconnecting");
        // Cancel during the long reconnect delay
        client.disconnect();
        expect(client.state).toBe("disconnected");
        let reconnected = false;
        client.on("connected", () => {
            reconnected = true;
        });
        client.on("reconnected", () => {
            reconnected = true;
        });
        await delay(3000);
        expect(reconnected).toBe(false);
        expect(client.state).toBe("disconnected");
    }, 10000);
});
//# sourceMappingURL=reconnection.test.js.map