// @vitest-environment jsdom

import React, { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

const mocks = vi.hoisted(() => ({
  useAuthQuery: vi.fn(),
  resolveUrls: vi.fn(async () => ({})),
  useTranslations: vi.fn(),
}));

const translations = {
  previous: "Zurueck",
  next: "Weiter",
  pageOf: "Seite {page} von {totalPages}",
} as const;

vi.mock("convex/react", () => ({
  useMutation: () => mocks.resolveUrls,
}));

vi.mock("@/hooks/use-auth-query", () => ({
  useAuthQuery: (...args: unknown[]) => mocks.useAuthQuery(...args),
}));

vi.mock("next-intl", () => ({
  useTranslations: (...args: unknown[]) => mocks.useTranslations(...args),
}));

vi.mock("@/components/ui/dialog", () => ({
  Dialog: ({ open, children }: { open: boolean; children: React.ReactNode }) =>
    open ? <div>{children}</div> : null,
  DialogContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
  DialogHeader: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
  DialogTitle: ({ children }: { children: React.ReactNode }) => <h2>{children}</h2>,
  DialogDescription: ({ children }: { children: React.ReactNode }) => <p>{children}</p>,
}));

import { MediaLibraryDialog } from "@/components/media/media-library-dialog";

(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;

function makeItems(count: number, page = 1) {
  return Array.from({ length: count }).map((_, index) => ({
    kind: "image" as const,
    source: "upload" as const,
    filename: `Item ${page}-${index + 1}`,
    previewUrl: `https://cdn.example.com/${page}-${index + 1}.jpg`,
    width: 1200,
    height: 800,
    createdAt: page * 1000 + index,
  }));
}

describe("MediaLibraryDialog", () => {
  let container: HTMLDivElement | null = null;
  let root: Root | null = null;

  beforeEach(() => {
    mocks.useAuthQuery.mockReset();
    mocks.resolveUrls.mockReset();
    mocks.useTranslations.mockReset();
    mocks.resolveUrls.mockImplementation(async () => ({}));
    const translate = (
      key: keyof typeof translations,
      values?: Record<string, string | number>,
    ) => {
      const template = translations[key] ?? key;
      if (!values) {
        return template;
      }

      return template.replace(/\{(\w+)\}/g, (_, token: string) => {
        const value = values[token];
        return value === undefined ? `{${token}}` : String(value);
      });
    };
    mocks.useTranslations.mockReturnValue(translate);

    container = document.createElement("div");
    document.body.appendChild(container);
    root = createRoot(container);
  });

  afterEach(async () => {
    if (root) {
      await act(async () => {
        root?.unmount();
      });
    }
    container?.remove();
    container = null;
    root = null;
  });

  it("calls media library query with page and default pageSize 8", async () => {
    mocks.useAuthQuery.mockReturnValue(undefined);

    await act(async () => {
      root?.render(
        <MediaLibraryDialog open onOpenChange={() => undefined} kindFilter="image" />,
      );
    });

    const firstCallArgs = mocks.useAuthQuery.mock.calls[0]?.[1];
    expect(firstCallArgs).toEqual(
      expect.objectContaining({
        page: 1,
        pageSize: 8,
        kindFilter: "image",
      }),
    );
  });

  it("renders at most 8 cards and shows localized pagination footer", async () => {
    mocks.useAuthQuery.mockReturnValue({
      items: makeItems(10),
      page: 1,
      pageSize: 8,
      totalPages: 3,
      totalCount: 24,
    });

    await act(async () => {
      root?.render(<MediaLibraryDialog open onOpenChange={() => undefined} />);
    });

    const cards = document.querySelectorAll("img[alt^='Item 1-']");
    expect(cards).toHaveLength(8);

    expect(document.body.textContent).toContain("Zurueck");
    expect(document.body.textContent).toContain("Seite 1 von 3");
    expect(document.body.textContent).toContain("Weiter");
  });

  it("updates query args when clicking next and previous", async () => {
    const responseByPage = new Map<number, {
      items: ReturnType<typeof makeItems>;
      page: number;
      pageSize: number;
      totalPages: number;
      totalCount: number;
    }>();

    mocks.useAuthQuery.mockImplementation((_, args: { page: number; pageSize: number }) => {
      if (!responseByPage.has(args.page)) {
        responseByPage.set(args.page, {
          items: makeItems(8, args.page),
          page: args.page,
          pageSize: args.pageSize,
          totalPages: 3,
          totalCount: 24,
        });
      }

      return responseByPage.get(args.page);
    });

    await act(async () => {
      root?.render(<MediaLibraryDialog open onOpenChange={() => undefined} />);
    });

    const nextButton = Array.from(document.querySelectorAll("button")).find(
      (button) => button.textContent?.trim() === "Weiter",
    );
    if (!(nextButton instanceof HTMLButtonElement)) {
      throw new Error("Next button not found");
    }

    await act(async () => {
      nextButton.click();
    });

    const nextCallArgs = mocks.useAuthQuery.mock.calls.at(-1)?.[1];
    expect(nextCallArgs).toEqual(expect.objectContaining({ page: 2, pageSize: 8 }));

    const previousButton = Array.from(document.querySelectorAll("button")).find(
      (button) => button.textContent?.trim() === "Zurueck",
    );
    if (!(previousButton instanceof HTMLButtonElement)) {
      throw new Error("Previous button not found");
    }

    await act(async () => {
      previousButton.click();
    });

    const previousCallArgs = mocks.useAuthQuery.mock.calls.at(-1)?.[1];
    expect(previousCallArgs).toEqual(expect.objectContaining({ page: 1, pageSize: 8 }));
  });

  it("renders 8 loading skeleton cards", async () => {
    mocks.useAuthQuery.mockReturnValue(undefined);

    await act(async () => {
      root?.render(<MediaLibraryDialog open onOpenChange={() => undefined} />);
    });

    expect(document.querySelectorAll(".aspect-square.animate-pulse.bg-muted")).toHaveLength(8);
  });
});
