import { Suspense } from "react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom/vitest";
import { renderWithProviders } from "../utils/test-utils";
import LoginForm from "../../app/components/modals/Login/LoginForm";

const { navMock } = vi.hoisted(() => ({
  navMock: {
    /** Default: marketing route — header modal is the primary entry (not `/login`). */
    pathname: "/",
    searchParams: new URLSearchParams(),
    replace: vi.fn(),
  },
}));

vi.mock("next/navigation", () => ({
  useRouter: () => ({
    replace: navMock.replace,
    push: vi.fn(),
    prefetch: vi.fn(),
    refresh: vi.fn(),
    back: vi.fn(),
    forward: vi.fn(),
  }),
  usePathname: () => navMock.pathname,
  useSearchParams: () => navMock.searchParams,
}));

vi.mock("../../lib/create/api", () => ({
  requestMagicLink: vi.fn(),
}));

vi.mock("../../app/(app)/create/utils/anonymousDraftStorage", async (importOriginal) => {
  const actual =
    await importOriginal<
      typeof import("../../app/(app)/create/utils/anonymousDraftStorage")
    >();
  return {
    ...actual,
    setTransferPendingFlag: vi.fn(),
  };
});

import { EMAIL_MAX_LEN } from "../../lib/create/isValidCreateFlowSaveEmail";
import { requestMagicLink } from "../../lib/create/api";
import { setTransferPendingFlag } from "../../app/(app)/create/utils/anonymousDraftStorage";

function renderLoginForm() {
  return renderWithProviders(
    <Suspense fallback={null}>
      <LoginForm />
    </Suspense>,
  );
}

describe("LoginForm", () => {
  beforeEach(() => {
    vi.mocked(requestMagicLink).mockReset();
    vi.mocked(setTransferPendingFlag).mockReset();
    navMock.replace.mockReset();
    navMock.pathname = "/";
    navMock.searchParams = new URLSearchParams();
  });

  it("renders title, email field, and submit control", () => {
    renderLoginForm();
    expect(
      screen.getByRole("heading", { name: /log in to communityrule/i }),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("textbox", { name: /email address/i }),
    ).toHaveAttribute("maxLength", String(EMAIL_MAX_LEN));
    expect(
      screen.getByRole("button", { name: /send me a magic link/i }),
    ).toBeInTheDocument();
  });

  it("links terms and privacy to the legal pages", () => {
    renderLoginForm();
    expect(
      screen.getByRole("link", { name: "Terms of Service" }),
    ).toHaveAttribute("href", "/terms");
    expect(
      screen.getByRole("link", { name: "Privacy Policy" }),
    ).toHaveAttribute("href", "/privacy");
  });

  it("shows validation error when email is invalid", async () => {
    const user = userEvent.setup();
    renderLoginForm();
    await user.type(
      screen.getByRole("textbox", { name: /email address/i }),
      "not-an-email",
    );
    await user.click(
      screen.getByRole("button", { name: /send me a magic link/i }),
    );
    expect(
      await screen.findByText(/enter a valid email address/i),
    ).toBeInTheDocument();
    expect(requestMagicLink).not.toHaveBeenCalled();
  });

  it("submits trimmed email and shows success state when API succeeds", async () => {
    const user = userEvent.setup();
    vi.mocked(requestMagicLink).mockResolvedValue({ ok: true });
    renderLoginForm();
    await user.type(
      screen.getByRole("textbox", { name: /email address/i }),
      "  Pat@Example.COM  ",
    );
    await user.click(
      screen.getByRole("button", { name: /send me a magic link/i }),
    );
    await waitFor(() => {
      expect(requestMagicLink).toHaveBeenCalledWith(
        "pat@example.com",
        "/",
        undefined,
      );
    });
    expect(
      await screen.findByRole("heading", { name: /check your email/i }),
    ).toBeInTheDocument();
    expect(screen.getByText(/we sent a sign-in link/i)).toBeInTheDocument();
    expect(screen.getByText(/check spam or promotions/i)).toBeInTheDocument();
  });

  it("submits a long email without treating length as invalid", async () => {
    const user = userEvent.setup();
    const email =
      "very.long.local-part.for.testing@subdomain.example.communityrule.org";
    vi.mocked(requestMagicLink).mockResolvedValue({ ok: true });
    renderLoginForm();
    await user.type(
      screen.getByRole("textbox", { name: /email address/i }),
      email,
    );
    await user.click(
      screen.getByRole("button", { name: /send me a magic link/i }),
    );
    await waitFor(() => {
      expect(requestMagicLink).toHaveBeenCalledWith(email, "/", undefined);
    });
  });

  it("saveProgress variant uses magicLinkNextPath and sets transfer pending on success", async () => {
    const user = userEvent.setup();
    vi.mocked(requestMagicLink).mockResolvedValue({ ok: true });
    renderWithProviders(
      <Suspense fallback={null}>
        <LoginForm
          variant="saveProgress"
          magicLinkNextPath="/create/community-structure?syncDraft=1"
        />
      </Suspense>,
    );
    await user.type(
      screen.getByRole("textbox", { name: /email address/i }),
      "save@example.com",
    );
    await user.click(
      screen.getByRole("button", { name: /send me a magic link/i }),
    );
    await waitFor(() => {
      expect(requestMagicLink).toHaveBeenCalledWith(
        "save@example.com",
        "/create/community-structure?syncDraft=1",
        undefined,
      );
    });
    expect(setTransferPendingFlag).toHaveBeenCalled();
  });

  it("passes safe next path when next query param is set", async () => {
    const user = userEvent.setup();
    navMock.searchParams = new URLSearchParams("next=/learn");
    vi.mocked(requestMagicLink).mockResolvedValue({ ok: true });
    renderLoginForm();
    await user.type(
      screen.getByRole("textbox", { name: /email address/i }),
      "a@b.co",
    );
    await user.click(
      screen.getByRole("button", { name: /send me a magic link/i }),
    );
    await waitFor(() => {
      expect(requestMagicLink).toHaveBeenCalledWith(
        "a@b.co",
        "/learn",
        undefined,
      );
    });
  });

  it("shows API error when request fails", async () => {
    const user = userEvent.setup();
    vi.mocked(requestMagicLink).mockResolvedValue({
      ok: false,
      error: "Server says no",
    });
    renderLoginForm();
    await user.type(
      screen.getByRole("textbox", { name: /email address/i }),
      "ok@example.com",
    );
    await user.click(
      screen.getByRole("button", { name: /send me a magic link/i }),
    );
    expect(await screen.findByText("Server says no")).toBeInTheDocument();
  });

  it("shows rate limit message when retryAfterMs is present", async () => {
    const user = userEvent.setup();
    vi.mocked(requestMagicLink).mockResolvedValue({
      ok: false,
      error: "Too many",
      retryAfterMs: 3500,
    });
    renderLoginForm();
    await user.type(
      screen.getByRole("textbox", { name: /email address/i }),
      "ok@example.com",
    );
    await user.click(
      screen.getByRole("button", { name: /send me a magic link/i }),
    );
    expect(
      await screen.findByText(/try again in 4 seconds/i),
    ).toBeInTheDocument();
  });

  it("shows URL-driven error for expired_link", () => {
    navMock.searchParams = new URLSearchParams("error=expired_link");
    renderLoginForm();
    expect(
      screen.getByText(/that sign-in link has expired/i),
    ).toBeInTheDocument();
  });

  it("shows URL-driven error for invalid_link", () => {
    navMock.searchParams = new URLSearchParams("error=invalid_link");
    renderLoginForm();
    expect(
      screen.getByText(/that sign-in link is not valid/i),
    ).toBeInTheDocument();
  });

  it("calls router.replace to clear error query when user types (full-page /login)", async () => {
    const user = userEvent.setup();
    navMock.pathname = "/login";
    navMock.searchParams = new URLSearchParams("error=expired_link");
    renderLoginForm();
    await user.type(
      screen.getByRole("textbox", { name: /email address/i }),
      "x",
    );
    expect(navMock.replace).toHaveBeenCalledWith("/login", { scroll: false });
  });

  it("clears error query using current pathname when not on /login", async () => {
    const user = userEvent.setup();
    navMock.pathname = "/learn";
    navMock.searchParams = new URLSearchParams("error=expired_link");
    renderLoginForm();
    await user.type(
      screen.getByRole("textbox", { name: /email address/i }),
      "x",
    );
    expect(navMock.replace).toHaveBeenCalledWith("/learn", { scroll: false });
  });

  it("shows network error when request throws", async () => {
    const user = userEvent.setup();
    vi.mocked(requestMagicLink).mockRejectedValue(new Error("network"));
    renderLoginForm();
    await user.type(
      screen.getByRole("textbox", { name: /email address/i }),
      "ok@example.com",
    );
    await user.click(
      screen.getByRole("button", { name: /send me a magic link/i }),
    );
    expect(
      await screen.findByText(/check your connection/i),
    ).toBeInTheDocument();
  });
});
