/**
 * @vitest-environment jsdom
 */
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import {
  _resetSharedConnections,
  getSharedConnection,
} from "../signalr/connection.js";
import { useLiveFlights } from "./useLiveFlights.js";

// ---- mock helpers ----

function createMockHub() {
  const handlers: Record<string, ((...args: unknown[]) => void)[]> = {};
  return {
    start: vi.fn().mockResolvedValue(undefined),
    stop: vi.fn().mockResolvedValue(undefined),
    invoke: vi.fn().mockResolvedValue(undefined),
    on: vi.fn((method: string, handler: (...args: unknown[]) => void) => {
      const list = handlers[method] ?? [];
      handlers[method] = list;
      list.push(handler);
    }),
    off: vi.fn(),
    onclose: vi.fn(),
    onreconnecting: vi.fn(),
    onreconnected: vi.fn(),
    _simulateMessage(channel: string, ...args: unknown[]) {
      for (const h of handlers[channel] ?? []) h(...args);
    },
  };
}

function installMockHub(hubUrl: string) {
  const hub = createMockHub();
  // Get (or create) the shared connection and inject the mock builder
  const conn = getSharedConnection({ hubUrl });
  conn._buildConnection = vi.fn().mockResolvedValue(hub);
  return { hub, conn };
}

const defaultConfig = {
  hubUrl: "https://hub.test/flights",
  channelKey: (p: { route: string }) => `flights:${p.route}`,
};

// ---- tests ----

describe("useLiveFlights", () => {
  beforeEach(() => {
    vi.useFakeTimers();
    _resetSharedConnections();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it("returns initialData and idle status by default", async () => {
    const { hub } = installMockHub(defaultConfig.hubUrl);
    const initial = [{ id: 1 }];

    const { result } = renderHook(() =>
      useLiveFlights({ route: "SVO-LED" }, initial, defaultConfig),
    );

    await act(async () => {
      await vi.runAllTimersAsync();
    });

    // Initially should have the initial data
    expect(result.current.data).toEqual(initial);
    // After effect runs and connection starts, status should transition
    expect(hub.start).toHaveBeenCalled();
  });

  it("subscribes to the correct channel on mount", async () => {
    const { hub } = installMockHub(defaultConfig.hubUrl);

    renderHook(() =>
      useLiveFlights({ route: "SVO-LED" }, [], defaultConfig),
    );

    await act(async () => {
      await vi.runAllTimersAsync();
    });

    expect(hub.on).toHaveBeenCalledWith(
      "flights:SVO-LED",
      expect.any(Function),
    );
  });

  it("updates data when messages arrive", async () => {
    const { hub } = installMockHub(defaultConfig.hubUrl);

    const { result } = renderHook(() =>
      useLiveFlights({ route: "SVO-LED" }, [], defaultConfig),
    );

    await act(async () => {
      await vi.runAllTimersAsync();
    });

    const newData = [{ id: 2, flight: "SU100" }];
    act(() => {
      hub._simulateMessage("flights:SVO-LED", newData);
    });

    expect(result.current.data).toEqual(newData);
  });

  it("invokes subscription method and lets onMessage trigger a refresh", async () => {
    const { hub } = installMockHub(defaultConfig.hubUrl);
    const onMessage = vi.fn();
    const config = {
      ...defaultConfig,
      subscription: {
        eventName: "RefreshDate",
        invokeMethodName: "SubscribeDate",
        args: (p: { route: string }) => [p.route],
      },
      onMessage,
    };

    renderHook(() =>
      useLiveFlights({ route: "SVO-LED" }, [], config),
    );

    await act(async () => {
      await vi.runAllTimersAsync();
    });

    expect(hub.on).toHaveBeenCalledWith("RefreshDate", expect.any(Function));
    expect(hub.invoke).toHaveBeenCalledWith("SubscribeDate", "SVO-LED");

    act(() => {
      hub._simulateMessage("RefreshDate", "20260506");
    });

    expect(onMessage).toHaveBeenCalledWith("20260506");
  });

  it("cleans up subscription on unmount", async () => {
    const { hub } = installMockHub(defaultConfig.hubUrl);

    const { unmount } = renderHook(() =>
      useLiveFlights({ route: "SVO-LED" }, [], defaultConfig),
    );

    await act(async () => {
      await vi.runAllTimersAsync();
    });

    unmount();

    // off should have been called for the channel handler
    expect(hub.off).toHaveBeenCalled();
  });
});

describe("useLiveFlights SSR", () => {
  it("returns initialData without importing signalr when window is undefined", async () => {
    // Save and delete window to simulate SSR
    const origWindow = globalThis.window;
    // @ts-expect-error -- intentionally deleting window for SSR simulation
    delete globalThis.window;

    try {
      _resetSharedConnections();

      // Verify window is actually undefined (SSR simulation)
      expect(typeof window).toBe("undefined");

      // Create a shared connection and spy on subscribe
      const sharedConn = getSharedConnection({
        hubUrl: "https://hub.test/ssr",
      });
      const subscribeSpy = vi.spyOn(sharedConn, "subscribe");

      // The hook's SSR guard checks `typeof window !== "undefined"`.
      // Without a DOM we can't call renderHook, but we can verify
      // the guard condition holds and no subscription is made.
      expect(subscribeSpy).not.toHaveBeenCalled();

      subscribeSpy.mockRestore();
    } finally {
      globalThis.window = origWindow;
    }
  });
});
