import { io, Socket } from "socket.io-client";
import { wsService } from "./websocket";

// Mock socket.io-client
jest.mock("socket.io-client");
const mockIo = io as jest.MockedFunction<typeof io>;
const mockSocket = {
  connected: false,
  on: jest.fn(),
  emit: jest.fn(),
  disconnect: jest.fn(),
  connect: jest.fn()
} as jest.Mocked<Socket>;

mockIo.mockReturnValue(mockSocket);

describe("WebSocketService", () => {
  beforeEach(() => {
    jest.clearAllMocks();
    // Reset the singleton instance
    (wsService as any).socket = null;
    mockSocket.connected = false;
  });

  afterEach(() => {
    delete process.env.NEXT_PUBLIC_WATCH_FINISHED_API;
  });

  describe("connect", () => {
    it("should create a new socket connection", () => {
      process.env.NEXT_PUBLIC_WATCH_FINISHED_API = "http://localhost:3001";

      const socket = wsService.connect();

      expect(mockIo).toHaveBeenCalledWith("http://localhost:3001", {
        transports: ["websocket", "polling"]
      });
      expect(socket).toBe(mockSocket);
    });

    it("should use custom URL when provided", () => {
      const socket = wsService.connect("ws://custom-url:8080");

      expect(mockIo).toHaveBeenCalledWith("ws://custom-url:8080", {
        transports: ["websocket", "polling"]
      });
    });

    it("should return existing connected socket", () => {
      mockSocket.connected = true;
      (wsService as any).socket = mockSocket;

      const socket = wsService.connect();

      expect(mockIo).toHaveBeenCalledTimes(0);
      expect(socket).toBe(mockSocket);
    });

    it("should set up event listeners", () => {
      wsService.connect();

      expect(mockSocket.on).toHaveBeenCalledWith(
        "connect",
        expect.any(Function)
      );
      expect(mockSocket.on).toHaveBeenCalledWith(
        "disconnect",
        expect.any(Function)
      );
      expect(mockSocket.on).toHaveBeenCalledWith(
        "connect_error",
        expect.any(Function)
      );
      expect(mockSocket.on).toHaveBeenCalledWith(
        "taskUpdate",
        expect.any(Function)
      );
      expect(mockSocket.on).toHaveBeenCalledWith(
        "fileUpdate",
        expect.any(Function)
      );
      expect(mockSocket.on).toHaveBeenCalledWith(
        "watcherUpdate",
        expect.any(Function)
      );
    });

    it("should dispatch custom events for WebSocket messages", () => {
      const dispatchEventSpy = jest.spyOn(window, "dispatchEvent");

      wsService.connect();

      // Get the taskUpdate event handler
      const taskUpdateHandler = mockSocket.on.mock.calls.find(
        (call) => call[0] === "taskUpdate"
      )[1];
      const mockEvent = {
        type: "settings",
        task: "update",
        settings: { key: "value" }
      };

      taskUpdateHandler(mockEvent);

      expect(dispatchEventSpy).toHaveBeenCalledWith(
        expect.objectContaining({
          type: "taskUpdate",
          detail: mockEvent
        })
      );

      dispatchEventSpy.mockRestore();
    });
  });

  describe("disconnect", () => {
    it("should disconnect the socket", () => {
      (wsService as any).socket = mockSocket;

      wsService.disconnect();

      expect(mockSocket.disconnect).toHaveBeenCalled();
      expect((wsService as any).socket).toBeNull();
    });

    it("should handle disconnect when no socket exists", () => {
      expect(() => wsService.disconnect()).not.toThrow();
    });
  });

  describe("joinRoom", () => {
    it("should emit join event with room", () => {
      (wsService as any).socket = mockSocket;

      wsService.joinRoom("testRoom");

      expect(mockSocket.emit).toHaveBeenCalledWith("join", {
        room: "testRoom"
      });
    });

    it("should not emit if no socket", () => {
      wsService.joinRoom("testRoom");

      expect(mockSocket.emit).not.toHaveBeenCalled();
    });
  });

  describe("leaveRoom", () => {
    it("should emit leave event with room", () => {
      (wsService as any).socket = mockSocket;

      wsService.leaveRoom("testRoom");

      expect(mockSocket.emit).toHaveBeenCalledWith("leave", {
        room: "testRoom"
      });
    });

    it("should not emit if no socket", () => {
      wsService.leaveRoom("testRoom");

      expect(mockSocket.emit).not.toHaveBeenCalled();
    });
  });

  describe("isConnected", () => {
    it("should return true when socket is connected", () => {
      (wsService as any).socket = mockSocket;
      mockSocket.connected = true;

      expect(wsService.isConnected).toBe(true);
    });

    it("should return false when socket is not connected", () => {
      (wsService as any).socket = mockSocket;
      mockSocket.connected = false;

      expect(wsService.isConnected).toBe(false);
    });

    it("should return false when no socket", () => {
      expect(wsService.isConnected).toBe(false);
    });
  });

  describe("reconnection logic", () => {
    it("should handle connection errors", () => {
      wsService.connect();

      // Get the connect_error handler
      const connectErrorHandler = mockSocket.on.mock.calls.find(
        (call) => call[0] === "connect_error"
      )[1];

      // Should not throw when handling connection error
      expect(() =>
        connectErrorHandler(new Error("Connection failed"))
      ).not.toThrow();
    });
  });
});
