/**
 * @vitest-environment jsdom
 */

import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, waitFor } from "@testing-library/react";
import type { ReactNode } from "react";
import { useDictionaries } from "./useDictionaries.js";
import { ApiClientProvider } from "@/shared/api/provider.js";
import type { ApiClient } from "@/shared/api/client.js";
import type { IRawDictionaries } from "./types.js";

const raw: IRawDictionaries = {
  regions: [{ world_region_id: 500374, title: { ru: "Россия" } }],
  countries: [{ code: "RU", title: { ru: "Россия" }, world_region_id: 500374 }],
  cities: [
    { code: "MOW", title: { ru: "Москва" }, country_code: "RU", has_afl_flights: true, location: { lat: 55, lon: 37 } },
  ],
  airports: [
    { code: "SVO", city_code: "MOW", title: { ru: "Шереметьево" }, has_afl_flights: true, location: { lat: 55, lon: 37 } },
  ],
};

function makeClient(
  impl: (path: string) => Promise<unknown>,
): ApiClient {
  return { get: vi.fn(impl) } as unknown as ApiClient;
}

function wrapper(client: ApiClient) {
  return ({ children }: { children: ReactNode }) => (
    <ApiClientProvider client={client}>{children}</ApiClientProvider>
  );
}

describe("useDictionaries", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("starts in a loading state with no data and no error", () => {
    const client = makeClient(async () => []);
    const { result } = renderHook(() => useDictionaries("ru"), {
      wrapper: wrapper(client),
    });
    expect(result.current.loading).toBe(true);
    expect(result.current.dictionaries).toBeNull();
    expect(result.current.error).toBeNull();
  });

  it("resolves to a populated IDictionaries after fetch", async () => {
    const client = makeClient(async (path: string) => {
      if (path.includes("world_regions")) return raw.regions;
      if (path.includes("countries")) return raw.countries;
      if (path.includes("cities")) return raw.cities;
      if (path.includes("airports")) return raw.airports;
      throw new Error("unexpected");
    });

    const { result } = renderHook(() => useDictionaries("ru"), {
      wrapper: wrapper(client),
    });

    await waitFor(() => expect(result.current.loading).toBe(false));

    expect(result.current.dictionaries).not.toBeNull();
    expect(result.current.dictionaries!.cities.map((c) => c.code)).toEqual(["MOW"]);
    expect(result.current.error).toBeNull();
  });

  it("sets error and keeps dictionaries null on fetch rejection", async () => {
    const client = makeClient(async () => {
      throw new Error("boom");
    });

    const { result } = renderHook(() => useDictionaries("ru"), {
      wrapper: wrapper(client),
    });

    await waitFor(() => expect(result.current.loading).toBe(false));

    expect(result.current.dictionaries).toBeNull();
    expect(result.current.error?.message).toBe("boom");
  });

  it("does not call setState after unmount during an in-flight fetch", async () => {
    let resolveFetch: (v: unknown) => void = () => {};
    const deferred = new Promise((r) => {
      resolveFetch = r;
    });
    const client = makeClient(async () => deferred);

    const warn = vi.spyOn(console, "error").mockImplementation(() => {});
    const { unmount } = renderHook(() => useDictionaries("ru"), {
      wrapper: wrapper(client),
    });

    unmount();
    resolveFetch([]);
    await Promise.resolve();
    await Promise.resolve();

    expect(warn).not.toHaveBeenCalledWith(
      expect.stringContaining("unmounted"),
    );
    warn.mockRestore();
  });
});
