import { SocketWrapper } from "./socketwrapper";

function pendingWithoutSocketServer(): void {
  if (!process.env.SOCKETSERVER_ENABLED) {
    pending("Set SOCKETSERVER_ENABLED to enable socket tests");
  }
}

describe("SocketWrapper", () => {
  const socketServerUrlNonExisting = "ws://localhost:4443/websocket";
  const socketServerUrl = "ws://localhost:4444/websocket";
  const socketServerUrlSlow = "ws://localhost:4445/websocket";

  it("can be constructed", () => {
    const socket = new SocketWrapper(socketServerUrl, fail, fail);
    expect(socket).toBeTruthy();
  });

  it("can connect", (done) => {
    pendingWithoutSocketServer();

    const socket = new SocketWrapper(
      socketServerUrl,
      () => done.fail("Got unexpected message event"),
      (error) => done.fail(error.message || "Unknown socket error"),
      () => {
        socket.disconnect();
        done();
      },
    );
    expect(socket).toBeTruthy();
    socket.connect();
  });

  it("fails to connect to non-existing server", (done) => {
    pendingWithoutSocketServer();

    const socket = new SocketWrapper(
      socketServerUrlNonExisting,
      () => done.fail("Got unexpected message event"),
      (error) => {
        if (error.message) {
          // error message only available in nodejs
          expect(error.message).toMatch(/ECONNREFUSED/i);
        }
        done();
      },
      () => done.fail("Got unexpected open event"),
    );
    expect(socket).toBeTruthy();
    socket.connect();
  });

  it("fails to connect to non-existing server but timeout is not triggered", (done) => {
    pendingWithoutSocketServer();
    const timeout = 1200; // ms

    const socket = new SocketWrapper(
      socketServerUrlNonExisting,
      () => done.fail("Got unexpected message event"),
      (error) => {
        expect(error).toBeTruthy();

        // All done. Delay test end to ensure the timeout is not triggered
        setTimeout(done, timeout * 1.3);
      },
      () => done.fail("Got unexpected open event"),
      () => 0,
      timeout,
    );
    expect(socket).toBeTruthy();
    socket.connect();
  });

  it("can connect to slow server", (done) => {
    pendingWithoutSocketServer();

    const socket = new SocketWrapper(
      socketServerUrlSlow,
      () => done.fail("Got unexpected message event"),
      (error) => done.fail(error.message || "Unknown socket error"),
      () => {
        socket.disconnect();
        done();
      },
    );
    expect(socket).toBeTruthy();
    socket.connect();
  });

  it("times out when establishing connection takes too long", async () => {
    pendingWithoutSocketServer();

    const socket = new SocketWrapper(
      socketServerUrlSlow,
      () => fail("Got unexpected message event"),
      (error) => fail(error.message || "Unknown socket error"),
      () => fail("Got unexpected opened event"),
      () => fail("Got unexpected closed event"),
      2_000,
    );
    socket.connect();

    await socket.connected
      .then(() => fail("must not resolve"))
      .catch((error) => expect(error).toMatch(/connection attempt timed out/i));
  });

  it("can connect and disconnect", (done) => {
    pendingWithoutSocketServer();

    let opened = 0;

    const socket = new SocketWrapper(
      socketServerUrl,
      () => done.fail("Got unexpected message event"),
      (error) => done.fail(error.message || "Unknown socket error"),
      () => {
        opened += 1;
        socket.disconnect();
      },
      (closeEvent) => {
        expect(closeEvent.wasClean).toEqual(true);
        expect(closeEvent.code).toEqual(1000 /* Normal Closure */);

        expect(opened).toEqual(1);
        done();
      },
    );
    socket.connect();
  });

  it("can disconnect before waiting for open", (done) => {
    pendingWithoutSocketServer();

    const socket = new SocketWrapper(
      socketServerUrl,
      () => done.fail("Got unexpected message event"),
      (error) => done.fail(error.message || "Unknown socket error"),
      () => done.fail("Got unexpected open event"),
      (closeEvent) => {
        expect(closeEvent.wasClean).toEqual(false);
        expect(closeEvent.code).toEqual(4001);
        done();
      },
    );
    socket.connect();
    socket.disconnect();
  });

  it("can disconnect before waiting for open and timeout will not be triggered", (done) => {
    pendingWithoutSocketServer();
    const timeout = 500; // ms

    const socket = new SocketWrapper(
      socketServerUrl,
      () => done.fail("Got unexpected message event"),
      (error) => done.fail(error.message || "Unknown socket error"),
      () => done.fail("Got unexpected open event"),
      (closeEvent) => {
        expect(closeEvent.wasClean).toEqual(false);
        expect(closeEvent.code).toEqual(4001);

        // All done. Delay test end to ensure the timeout is not triggered
        setTimeout(done, timeout * 1.3);
      },
      timeout,
    );
    socket.connect();
    socket.disconnect();
  });

  it("can send events when connected", (done) => {
    pendingWithoutSocketServer();

    const responseMessages = new Array<string>();

    const socket = new SocketWrapper(
      socketServerUrl,
      (response) => {
        expect(response.type).toEqual("message");
        responseMessages.push(response.data);

        if (responseMessages.length === 3) {
          socket.disconnect();
        }
      },
      (error) => done.fail(error.message || "Unknown socket error"),
      async () => {
        await socket.send("aabbccdd");
        await socket.send("whatever");
        await socket.send("lalala");
      },
      () => {
        expect(responseMessages.length).toEqual(3);
        done();
      },
    );
    socket.connect();
  });

  it("can send events after timeout period", (done) => {
    pendingWithoutSocketServer();

    // The "timeout period" is the period in which a timeout could potentially be triggered

    const timeoutPeriodLength = 1_500;

    const socket = new SocketWrapper(
      socketServerUrl,
      (response) => {
        expect(response.type).toEqual("message");
        expect(response.data).toEqual("Hello world");
        socket.disconnect();
      },
      (error) => done.fail(error.message || "Unknown socket error"),
      undefined,
      () => done(),
      timeoutPeriodLength,
    );
    socket.connect();

    setTimeout(() => socket.send("Hello world"), 2 * timeoutPeriodLength);
  });

  it("cannot send on a disconnect socket (it will never come back)", (done) => {
    pendingWithoutSocketServer();

    const socket = new SocketWrapper(
      socketServerUrl,
      () => done.fail("Got unexpected message event"),
      (error) => done.fail(error.message || "Unknown socket error"),
      () => {
        socket.disconnect();
      },
      () => {
        socket
          .send("la li lu")
          .then(() => done.fail("must not resolve"))
          .catch((error) => {
            expect(error).toMatch(/socket was closed/i);
            done();
          });
      },
    );
    socket.connect();
  });
});
