import { Socket } from "@hyper-fetch/sockets";
import { act, waitFor } from "@testing-library/react";
import { createWebsocketMockingServer, sleep, waitForConnection } from "@hyper-fetch/testing";

import { renderUseEmitter } from "../../utils/use-emitter.utils";

describe("useEmitter [ Base ]", () => {
  const { url, startServer, stopServer, expectEmitterEvent } = createWebsocketMockingServer();
  let socket = new Socket({ url });
  let emitter = socket.createEmitter<{ name: string; age: number }>()({ topic: "test/:testId" });

  beforeEach(async () => {
    startServer();
    socket = new Socket({ url });
    emitter = socket.createEmitter<{ name: string; age: number }>()({ topic: "test/:testId" });
    await waitForConnection(socket);

    vi.resetModules();
    vi.resetAllMocks();
  });

  afterEach(() => {
    stopServer();
  });

  describe("when hook emit event", () => {
    it("should set state with data", async () => {
      const myEmitter = emitter.setParams({ testId: "1" });

      const message = { name: "Maciej", age: 99 };
      const view = renderUseEmitter(myEmitter);

      act(() => {
        view.result.current.emit({ payload: message });
      });

      await expectEmitterEvent(myEmitter.setParams({ testId: "1" }), message);

      await waitFor(() => {
        expect(view.result.current.connected).toBeTrue();
        expect(view.result.current.connecting).toBeFalse();
      });
    });

    it("should handle emit error correctly", async () => {
      const error = new Error("Emit error");
      const view = renderUseEmitter(emitter);
      let capturedError: { error: Error } | null = null;

      view.result.current.onEmitError((err) => {
        capturedError = err;
      });

      act(() => {
        emitter.socket.events.emitEmitterError({ error, emitter });
      });

      await waitFor(() => {
        expect(capturedError).toEqual({ error });
      });
    });

    it("should trigger onEmit callback when emitting", async () => {
      const message = { name: "Test", age: 25 };
      const instance = emitter.setParams({ testId: "1" });
      const view = renderUseEmitter(instance);
      let emitCalled = false;

      view.result.current.onEmit(() => {
        emitCalled = true;
      });

      act(() => {
        view.result.current.emit({ payload: message });
      });

      await expectEmitterEvent(instance, message);

      expect(emitCalled).toBeTrue();
    });

    it("should handle undefined onEmit callback", async () => {
      const myEmitter = emitter.setParams({ testId: "1" });
      renderUseEmitter(myEmitter);

      // Should not throw when callback is undefined
      act(() => {
        myEmitter.socket.events.emitEmitterStartEvent({ emitter: myEmitter });
      });

      // No assertion needed - test passes if no error is thrown
      await sleep(10);
    });
  });

  describe("when emitter topic changes", () => {
    it("should update event listeners", async () => {
      const emitter1 = emitter.setParams({ testId: "1" });
      const view = renderUseEmitter(emitter1);
      let callCount = 0;

      view.result.current.onEmit(() => {
        callCount += 1;
      });

      act(() => {
        view.result.current.emit({ payload: { name: "Maciej", age: 99 } });
      });

      await expectEmitterEvent(emitter1, { name: "Maciej", age: 99 });
      expect(callCount).toBe(1);

      const emitter2 = socket.createEmitter()({ topic: "test/:testId" }).setParams({ testId: "2" });
      view.rerender({ emitter: emitter2 });

      act(() => {
        view.result.current.emit({ payload: { name: "Maciej", age: 12 } });
      });

      await expectEmitterEvent(emitter2, { name: "Maciej", age: 12 });
      expect(callCount).toBe(2);
    });
  });
});
