// @vitest-environment node

import * as React from "react";
import { describe, expect, it, vi } from "vitest";
// @ts-ignore
import { renderToString } from "react-dom/server";

import {
  QueryCache,
  QueryClientProvider,
  useInfiniteQuery,
  useQuery,
} from "../../react-query";
import { createQueryClient, queryKey, sleep } from "../utils";

describe("Server Side Rendering", () => {
  it("should not trigger fetch", () => {
    const queryCache = new QueryCache();
    const queryClient = createQueryClient({ queryCache });
    const key = queryKey();
    const queryFn = vi.fn<[], string>().mockReturnValue("data");

    function Page() {
      const query = useQuery(key, queryFn);

      const content = `status ${query.status}`;

      return (
        <div>
          <div>{content}</div>
        </div>
      );
    }

    const markup = renderToString(
      <QueryClientProvider client={queryClient}>
        <Page />
      </QueryClientProvider>
    );

    expect(markup).toContain("status loading");
    expect(queryFn).toHaveBeenCalledTimes(0);
    queryCache.clear();
  });

  it("should add prefetched data to cache", async () => {
    const queryCache = new QueryCache();
    const queryClient = createQueryClient({ queryCache });
    const key = queryKey();
    const fetchFn = () => Promise.resolve("data");
    const data = await queryClient.fetchQuery(key, fetchFn);
    expect(data).toBe("data");
    expect(queryCache.find(key)?.state.data).toBe("data");
    queryCache.clear();
  });

  it("should return existing data from the cache", async () => {
    const queryCache = new QueryCache();
    const queryClient = createQueryClient({ queryCache });
    const key = queryKey();
    const queryFn = vi.fn(() => {
      sleep(10);
      return "data";
    });

    function Page() {
      const query = useQuery(key, queryFn);

      const content = `status ${query.status}`;

      return (
        <div>
          <div>{content}</div>
        </div>
      );
    }

    await queryClient.prefetchQuery(key, queryFn);

    const markup = renderToString(
      <QueryClientProvider client={queryClient}>
        <Page />
      </QueryClientProvider>
    );

    expect(markup).toContain("status success");
    expect(queryFn).toHaveBeenCalledTimes(1);
    queryCache.clear();
  });

  it("should add initialData to the cache", () => {
    const key = queryKey();

    const queryCache = new QueryCache();
    const queryClient = createQueryClient({ queryCache });

    function Page() {
      const [page, setPage] = React.useState(1);
      const { data } = useQuery([key, page], async () => page, {
        initialData: 1,
      });

      return (
        <div>
          <h1 data-testid="title">{data}</h1>
          <button onClick={() => setPage(page + 1)}>next</button>
        </div>
      );
    }

    renderToString(
      <QueryClientProvider client={queryClient}>
        <Page />
      </QueryClientProvider>
    );

    const keys = queryCache.getAll().map((query) => query.queryKey);

    expect(keys).toEqual([[key, 1]]);
    queryCache.clear();
  });

  it("useInfiniteQuery should return the correct state", async () => {
    const queryCache = new QueryCache();
    const queryClient = createQueryClient({ queryCache });
    const key = queryKey();
    const queryFn = vi.fn(async () => {
      await sleep(5);
      return "page 1";
    });

    function Page() {
      const query = useInfiniteQuery(key, queryFn);
      return (
        <ul>
          {query.data?.pages.map((page) => (
            <li key={page}>{page}</li>
          ))}
        </ul>
      );
    }

    await queryClient.prefetchInfiniteQuery(key, queryFn);

    const markup = renderToString(
      <QueryClientProvider client={queryClient}>
        <Page />
      </QueryClientProvider>
    );

    expect(markup).toContain("page 1");
    expect(queryFn).toHaveBeenCalledTimes(1);
    queryCache.clear();
  });
});
