/**
 * @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 {
  buildFlightChannelKey,
  buildTrackerFlightSubscriptionKey,
  useLiveFlightDetails,
} from "./useLiveFlightDetails.js";
import type { ISimpleFlight, IFlightId, IFlightLeg, IParsedFlightId } from "../types.js";

// ---------------------------------------------------------------------------
// Env mock
// ---------------------------------------------------------------------------

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

// ---------------------------------------------------------------------------
// Mock hub 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();
  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("buildFlightChannelKey", () => {
  it("builds key without suffix", () => {
    const id: IParsedFlightId = {
      carrier: "SU",
      flightNumber: "100",
      date: "2025-01-15",
    };
    expect(buildFlightChannelKey(id)).toBe("flight:SU100@2025-01-15");
  });

  it("builds key with suffix", () => {
    const id: IParsedFlightId = {
      carrier: "SU",
      flightNumber: "100",
      suffix: "A",
      date: "2025-01-15",
    };
    expect(buildFlightChannelKey(id)).toBe("flight:SU100A@2025-01-15");
  });

  it("builds key with empty suffix", () => {
    const id: IParsedFlightId = {
      carrier: "SU",
      flightNumber: "100",
      suffix: "",
      date: "2025-01-15",
    };
    expect(buildFlightChannelKey(id)).toBe("flight:SU100@2025-01-15");
  });
});

describe("buildTrackerFlightSubscriptionKey", () => {
  it("uses compact URL date when dateLT is unavailable", () => {
    const id: IParsedFlightId = {
      carrier: "SU",
      flightNumber: "100",
      suffix: "A",
      date: "20250115",
    };

    expect(buildTrackerFlightSubscriptionKey(id)).toBe("SU100A@20250115");
  });

  it("uses API dateLT as-is when available", () => {
    const id: IFlightId = {
      carrier: "SU",
      flightNumber: "0100",
      suffix: "",
      date: "2025-01-15",
      dateLT: "2025-01-15T00:00:00",
    };

    expect(buildTrackerFlightSubscriptionKey(id)).toBe(
      "SU0100@2025-01-15T00:00:00",
    );
  });
});

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

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

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

  it("returns initial flight on mount", async () => {
    installMockHub(HUB_URL);
    const initial = makeFlight("f1");
    const id: IParsedFlightId = {
      carrier: "SU",
      flightNumber: "100",
      date: "2025-01-15",
    };

    const { result } = renderHook(() =>
      useLiveFlightDetails(id, initial),
    );

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

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

  it("returns null when initial flight is null", async () => {
    installMockHub(HUB_URL);
    const id: IParsedFlightId = {
      carrier: "SU",
      flightNumber: "100",
      date: "2025-01-15",
    };

    const { result } = renderHook(() =>
      useLiveFlightDetails(id, null),
    );

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

    expect(result.current.flight).toBeNull();
  });

  it("subscribes to Angular TrackerHub refresh and invokes Subscribe", async () => {
    const { hub } = installMockHub(HUB_URL);
    const id: IParsedFlightId = {
      carrier: "SU",
      flightNumber: "100",
      suffix: "A",
      date: "20250115",
    };

    renderHook(() => useLiveFlightDetails(id, null));

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

    expect(hub.on).toHaveBeenCalledWith("Refresh", expect.any(Function));
    expect(hub.invoke).toHaveBeenCalledWith("Subscribe", "SU100A@20250115");
  });

  it("updates flight when a legacy array message arrives", async () => {
    const { hub } = installMockHub(HUB_URL);
    const id: IParsedFlightId = {
      carrier: "SU",
      flightNumber: "100",
      date: "2025-01-15",
    };
    const initial = makeFlight("f1");

    const { result } = renderHook(() =>
      useLiveFlightDetails(id, initial),
    );

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

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

    // useLiveFlights replaces the full array; our hook takes [0]
    expect(result.current.flight).toEqual(updated[0]);
  });

  it("refreshes API data when TrackerHub sends Refresh", async () => {
    const { hub } = installMockHub(HUB_URL);
    const id: IParsedFlightId = {
      carrier: "SU",
      flightNumber: "100",
      date: "20250115",
    };
    const onRefresh = vi.fn();

    renderHook(() => useLiveFlightDetails(id, null, onRefresh));

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

    act(() => {
      hub._simulateMessage("Refresh", "SU0100@20250115");
    });

    expect(onRefresh).toHaveBeenCalledTimes(1);
  });

  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);

      const conn = getSharedConnection({ hubUrl: HUB_URL });
      const subscribeSpy = vi.spyOn(conn, "subscribe");

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