import { describe, it, expect, vi } from "vitest";
import { renderToString } from "react-dom/server";
import type { ReactNode, ReactElement } from "react";

/**
 * Guard: server-rendering critical surfaces must not produce ANY
 * console.error or console.warn from React. The most common sources are:
 *
 *  - Invalid HTML nesting (e.g. <div> inside <button>) — this is exactly
 *    what produced the recent "Hydration failed" runtime error.
 *  - Two children with the same `key`.
 *  - "Encountered a script tag while rendering React component" — a
 *    React 19 warning when a `<script>` appears inside the React tree
 *    rather than `<head>`.
 *
 * We can't fully reproduce *runtime* hydration in a Node test, but
 * `renderToString` runs React's SSR-time validation, which catches
 * these structural issues during the same render that would later
 * mismatch in the browser.
 */

// --- Mocks for next/link + the radix primitives we use --------------------
// These are deliberately TRANSPARENT (render children) so the underlying
// element tree we care about (button vs div nesting, etc.) reaches React's
// validator unchanged.

vi.mock("next/link", () => ({
  default: ({ href, children, ...rest }: { href: string; children: ReactNode } & Record<string, unknown>) => (
    <a href={href} {...rest}>
      {children}
    </a>
  ),
}));

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

import { AccountsListView, type AccountsListAccount } from "@/components/accounts-list-view";
import { EditMessageForm } from "@/components/reminder-edit/edit-message-form";

// next-themes / radix dropdown mocks for ThemeToggle.
const useThemeReturn: { theme: string | undefined; setTheme: () => void } = {
  theme: "system",
  setTheme: () => {},
};
vi.mock("next-themes", () => ({
  useTheme: () => useThemeReturn,
}));
vi.mock("@/components/ui/dropdown-menu", () => ({
  DropdownMenu: ({ children }: { children: ReactNode }) => <>{children}</>,
  DropdownMenuTrigger: ({ children }: { children: ReactNode; asChild?: boolean }) => <>{children}</>,
  DropdownMenuContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
  DropdownMenuItem: ({ children }: { children: ReactNode }) => (
    <div role="menuitem">{children}</div>
  ),
}));

// next/navigation is touched by the edit form's useRouter().
vi.mock("next/navigation", () => ({
  useRouter: () => ({ push: () => {} }),
}));

// Mock the server action import inside EditMessageForm so importing it
// doesn't try to wire up the real action machinery.
vi.mock("@/actions/reminders", () => ({
  updateReminderAction: () => Promise.resolve({ ok: true, reminderId: "r-1" }),
}));

import { ThemeToggle } from "@/components/theme-toggle";

// Helper: render with console.error / console.warn captured. Restores the
// originals via vi.restoreAllMocks at end of each test.
function renderQuiet(node: ReactElement): { html: string; errors: unknown[][]; warns: unknown[][] } {
  const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
  const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
  const html = renderToString(node);
  const errors = errorSpy.mock.calls;
  const warns = warnSpy.mock.calls;
  errorSpy.mockRestore();
  warnSpy.mockRestore();
  return { html, errors, warns };
}

const account: AccountsListAccount = {
  id: "a-1",
  label: "Personal",
  status: "connected",
  phoneNumber: "+60123456789",
  lastConnectedAt: new Date("2026-05-01T10:00:00Z"),
};

describe("SSR render — no React errors or warnings", () => {
  it("AccountsListView (populated) renders cleanly", () => {
    const { errors, warns } = renderQuiet(
      <AccountsListView accounts={[account]} />,
    );
    expect(errors).toEqual([]);
    expect(warns).toEqual([]);
  });

  it("AccountsListView (empty) renders cleanly", () => {
    const { errors, warns } = renderQuiet(
      <AccountsListView accounts={[]} />,
    );
    expect(errors).toEqual([]);
    expect(warns).toEqual([]);
  });

  it("ThemeToggle renders cleanly across all theme values", () => {
    for (const t of ["light", "dark", "system", undefined]) {
      useThemeReturn.theme = t;
      const { errors, warns } = renderQuiet(<ThemeToggle />);
      expect(errors, `console.error during theme=${t}`).toEqual([]);
      expect(warns, `console.warn during theme=${t}`).toEqual([]);
    }
  });

  it("EditMessageForm renders cleanly (text-only and media-attached)", () => {
    const baseProps = {
      reminderId: "r-1",
      accountId: "acc-1",
      groupIds: ["g-1"],
      scheduledAtIso: "2026-05-13T09:00:00.000+08:00",
      rrule: "FREQ=DAILY",
      timezone: "Asia/Kuala_Lumpur",
      name: "Existing name",
      initialMessages: [
        { kind: "text" as const, textContent: "Hello", mediaId: null },
      ],
    };
    const a = renderQuiet(<EditMessageForm {...baseProps} />);
    expect(a.errors).toEqual([]);
    expect(a.warns).toEqual([]);

    const b = renderQuiet(
      <EditMessageForm
        {...baseProps}
        initialMessages={[
          { kind: "media", textContent: "caption text", mediaId: "m-1" },
        ]}
        initialMediaInfo={{ "m-1": { filename: "f.pdf", mimeType: "application/pdf" } }}
      />,
    );
    expect(b.errors).toEqual([]);
    expect(b.warns).toEqual([]);
  });
});

describe("SSR markup — no <div> inside <button> (the bug we just fixed)", () => {
  it("AccountsListView never nests a div inside a button", () => {
    const { html } = renderQuiet(
      <AccountsListView accounts={[account]} />,
    );
    // Naive but effective: scan each <button>...</button> region for any
    // <div, <p, or <h-tag inside. Those are flow content and not allowed
    // inside a button per the HTML spec — browsers auto-close the button
    // and the SSR/client trees diverge.
    const buttons = [...html.matchAll(/<button\b[^>]*>([\s\S]*?)<\/button>/g)];
    for (const m of buttons) {
      const inner = m[1] ?? "";
      expect(
        /<(?:div|p|h[1-6])\b/.test(inner),
        `Invalid nesting inside <button>: ${inner.slice(0, 200)}`,
      ).toBe(false);
    }
  });

  it("EditMessageForm never nests a div inside a button", () => {
    const { html } = renderQuiet(
      <EditMessageForm
        reminderId="r-1"
        accountId="acc-1"
        groupIds={["g-1"]}
        scheduledAtIso="2026-05-13T09:00:00.000+08:00"
        rrule={null}
        timezone="Asia/Kuala_Lumpur"
        name="Existing name"
        initialMessages={[{ kind: "text", textContent: "Hello", mediaId: null }]}
      />,
    );
    const buttons = [...html.matchAll(/<button\b[^>]*>([\s\S]*?)<\/button>/g)];
    for (const m of buttons) {
      const inner = m[1] ?? "";
      expect(/<(?:div|p|h[1-6])\b/.test(inner)).toBe(false);
    }
  });
});
