/**
 * @vitest-environment jsdom
 */
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import {
  _resetSharedConnections,
  getSharedConnection,
} from "@/shared/signalr/connection.js";
import { buildBoardChannelKey, useLiveBoardSearch } from "./useLiveBoardSearch.js";
import type { LiveBoardSearchParams } from "./useLiveBoardSearch.js";
import type { ISimpleFlight, IFlightId, IFlightLeg } from "../types.js";

// ---------------------------------------------------------------------------
// Env mock — getEnv() must return SIGNALR_HUB_URL
// ---------------------------------------------------------------------------

vi.mock("@/env/index.js", () => ({
  getEnv: () => ({
    SIGNALR_HUB_URL: "https://hub.test/tracker",
  }),
}));

// ---------------------------------------------------------------------------
// Mock hub helpers (same pattern as useLiveFlights.test.ts)
// ---------------------------------------------------------------------------

function createMockHub() {
  const handlers: Record<string, ((...args: unknown[]) => void)[]> = {};
  return {
    start: vi.fn().mockResolvedValue(undefined),
    stop: 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();
  const conn = getSharedConnection({ hubUrl });
  conn._buildConnection = vi.fn().mockResolvedValue(hub);
  return { hub, conn };
}

// ---------------------------------------------------------------------------
// Test fixture
// ---------------------------------------------------------------------------

function makeFlight(id: string): ISimpleFlight {
  return {
    routeType: "Direct",
    flightId: {
      carrier: "SU",
      flightNumber: "100",
      suffix: "",
      date: "2025-01-15",
    } satisfies IFlightId,
    flyingTime: "3h",
    operatingBy: {},
    id,
    status: "Scheduled",
    leg: {
      arrival: {
        scheduled: {
          airport: "LED",
          airportCode: "LED",
          city: "St Petersburg",
          cityCode: "LED",
          countryCode: "RU",
        },
        times: {
          scheduledArrival: {
            dayChange: { value: 0, title: "" },
            local: "",
            localTime: "",
            tzOffset: 3,
            utc: "",
          },
        },
      },
      departure: {
        scheduled: {
          airport: "SVO",
          airportCode: "SVO",
          city: "Moscow",
          cityCode: "MOW",
          countryCode: "RU",
        },
        checkingStatus: "Open",
        times: {
          scheduledDeparture: {
            dayChange: { value: 0, title: "" },
            local: "",
            localTime: "",
            tzOffset: 3,
            utc: "",
          },
        },
      },
      dayChange: 0,
      equipment: {},
      flags: {
        checkinAvailable: true,
        returnToAirport: false,
        routeChanged: false,
      },
      flyingTime: "3h",
      index: 0,
      operatingBy: {},
      status: "Scheduled",
      updated: "2025-01-15T00:00:00Z",
    } satisfies IFlightLeg,
  };
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("buildBoardChannelKey", () => {
  it("builds key with date only", () => {
    expect(buildBoardChannelKey({ date: "20250115" })).toBe(
      "board:20250115::",
    );
  });

  it("builds key with date + departure", () => {
    expect(
      buildBoardChannelKey({ date: "20250115", departure: "SVO" }),
    ).toBe("board:20250115:SVO:");
  });

  it("builds key with date + departure + arrival", () => {
    expect(
      buildBoardChannelKey({
        date: "20250115",
        departure: "SVO",
        arrival: "LED",
      }),
    ).toBe("board:20250115:SVO:LED");
  });
});

describe("useLiveBoardSearch", () => {
  const HUB_URL = "https://hub.test/tracker";

  beforeEach(() => {
    vi.useFakeTimers();
    _resetSharedConnections();
  });

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

  it("returns initial flights on mount", async () => {
    installMockHub(HUB_URL);
    const initial = [makeFlight("f1")];
    const params: LiveBoardSearchParams = { date: "20250115" };

    const { result } = renderHook(() =>
      useLiveBoardSearch(params, initial),
    );

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

    expect(result.current.flights).toEqual(initial);
  });

  it("subscribes to the correct channel", async () => {
    const { hub } = installMockHub(HUB_URL);
    const params: LiveBoardSearchParams = {
      date: "20250115",
      departure: "SVO",
      arrival: "LED",
    };

    renderHook(() => useLiveBoardSearch(params, []));

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

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

  it("updates flights when a SignalR message arrives", async () => {
    const { hub } = installMockHub(HUB_URL);
    const params: LiveBoardSearchParams = { date: "20250115" };
    const initial = [makeFlight("f1")];
    const channel = "board:20250115::";

    const { result } = renderHook(() =>
      useLiveBoardSearch(params, initial),
    );

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

    const updated = [makeFlight("f2")];
    act(() => {
      hub._simulateMessage(channel, updated);
    });

    expect(result.current.flights).toEqual(updated);
  });

  it("returns idle connectionStatus during SSR", () => {
    const origWindow = globalThis.window;
    // @ts-expect-error -- intentionally deleting window for SSR simulation
    delete globalThis.window;

    try {
      _resetSharedConnections();
      installMockHub(HUB_URL);

      // Without window, renderHook won't work (no DOM),
      // but we can verify the SSR guard in useLiveFlights
      // by checking the underlying shared connection isn't subscribed
      const conn = getSharedConnection({ hubUrl: HUB_URL });
      const subscribeSpy = vi.spyOn(conn, "subscribe");

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