import { act, renderHook, waitFor } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { AUTH_TOKEN_KEY } from "@/lib/auth/client";
import { useModels } from "@/hooks/use-models";

let storage: Storage;

beforeEach(() => {
  const values = new Map<string, string>();
  storage = {
    get length() {
      return values.size;
    },
    clear: () => values.clear(),
    getItem: (key) => values.get(key) ?? null,
    key: (index) => [...values.keys()][index] ?? null,
    removeItem: (key) => values.delete(key),
    setItem: (key, value) => values.set(key, value),
  };
  Object.defineProperty(window, "localStorage", {
    configurable: true,
    value: storage,
  });
  storage.setItem(AUTH_TOKEN_KEY, "browser-user-token");
});

afterEach(() => {
  storage.clear();
  vi.unstubAllGlobals();
});

describe("useModels", () => {
  it("loads once for a user and retains a persisted model present in the catalog", async () => {
    const fetchMock = vi.fn().mockResolvedValue(
      Response.json({
        models: [
          { id: "auto", name: "Aegida Auto", description: "Auto" },
          { id: "qwen", name: "Qwen", description: "Qwen" },
        ],
      }),
    );
    vi.stubGlobal("fetch", fetchMock);

    const { result, rerender } = renderHook(
      ({ userId }) => useModels(userId, "qwen"),
      { initialProps: { userId: "user-1" } },
    );

    await waitFor(() => expect(result.current.status).toBe("ready"));
    expect(result.current.effectiveModelId).toBe("qwen");
    rerender({ userId: "user-1" });
    expect(fetchMock).toHaveBeenCalledOnce();
  });

  it("falls back to only auto when the catalog fails or the persisted model is absent", async () => {
    const fetchMock = vi
      .fn()
      .mockResolvedValueOnce(
        Response.json({
          models: [{ id: "auto", name: "Aegida Auto", description: "Auto" }],
        }),
      )
      .mockRejectedValueOnce(new Error("network-private-sentinel"));
    vi.stubGlobal("fetch", fetchMock);

    const first = renderHook(() => useModels("user-1", "removed-model"));
    await waitFor(() => expect(first.result.current.status).toBe("ready"));
    expect(first.result.current.models.map(({ id }) => id)).toEqual(["auto"]);
    expect(first.result.current.effectiveModelId).toBe("auto");
    first.unmount();

    const second = renderHook(() => useModels("user-2", "qwen"));
    await waitFor(() => expect(second.result.current.status).toBe("unavailable"));
    expect(second.result.current.models.map(({ id }) => id)).toEqual(["auto"]);
    expect(second.result.current.effectiveModelId).toBe("auto");
  });

  it("aborts a stale user request so it cannot overwrite the current catalog", async () => {
    const pending: Array<{
      signal: AbortSignal;
      resolve(response: Response): void;
    }> = [];
    vi.stubGlobal(
      "fetch",
      vi.fn((_input: RequestInfo | URL, init?: RequestInit) =>
        new Promise<Response>((resolve) => {
          pending.push({ signal: init?.signal as AbortSignal, resolve });
        }),
      ),
    );
    const { result, rerender } = renderHook(
      ({ userId, model }) => useModels(userId, model),
      { initialProps: { userId: "user-1", model: "old-model" } },
    );
    await waitFor(() => expect(pending).toHaveLength(1));

    rerender({ userId: "user-2", model: "new-model" });
    await waitFor(() => expect(pending).toHaveLength(2));
    expect(pending[0].signal.aborted).toBe(true);

    await act(async () => {
      pending[1].resolve(
        Response.json({
          models: [
            { id: "auto", name: "Aegida Auto", description: "Auto" },
            { id: "new-model", name: "New", description: "New" },
          ],
        }),
      );
    });
    await waitFor(() => expect(result.current.effectiveModelId).toBe("new-model"));

    await act(async () => {
      pending[0].resolve(
        Response.json({
          models: [
            { id: "auto", name: "Aegida Auto", description: "Auto" },
            { id: "old-model", name: "Old", description: "Old" },
          ],
        }),
      );
    });
    expect(result.current.effectiveModelId).toBe("new-model");
  });
});
